iceberg: support views (#10069)

* s3tables: tag table entries and exclude views from table listings

* s3tables: add view CRUD operations

* iceberg: support view create, load, exists, drop, and list

* iceberg: support view update

* iceberg: test view error classification and metadata round-trip

* iceberg: pre-check existence and write view metadata only after create

* iceberg: map view namespace-not-found to 404

* iceberg: test view create namespace-404 and duplicate no-clobber

* s3tables: tag view metadata and entry type atomically

CreateView wrote ExtendedKeyMetadata and ExtendedKeyEntryType in two
UpdateEntry calls, so a partial failure could leave a view directory
untagged. Add setExtendedAttributes to set both in one UpdateEntry.

* iceberg: roll back view registration when metadata write fails

The metadata file is written after the catalog registers the view. If
that write fails, drop the just-created view so it doesn't linger
pointing at a missing metadata.json. Reuse the DeleteView path via a
shared dropView helper.
This commit is contained in:
Chris Lu authored and GitHub committed 2026-06-23 15:22:31 -07:00
1 parent 1ca628d3e9
commit 0403e47ef6
13 files changed
+1815 -3

No files matched your search

+22
View File
@@ -26,6 +26,12 @@ const (
ExtendedKeyMetadataVersion = "s3tables.metadataVersion"
ExtendedKeyPolicy = "s3tables.policy"
ExtendedKeyTags = "s3tables.tags"
ExtendedKeyEntryType = "s3tables.entryType"
// Entry-type marker values for ExtendedKeyEntryType. Absent or "table" means
// a table; views are stored like tables but tagged "view".
EntryTypeTable = "table"
EntryTypeView = "view"
// Maximum request body size (10MB)
maxRequestBodySize = 10 * 1024 * 1024
@@ -153,6 +159,18 @@ func (h *S3TablesHandler) HandleRequest(w http.ResponseWriter, r *http.Request,
case "DeleteTable":
err = h.handleDeleteTable(w, r, filerClient)
// View operations
case "CreateView":
err = h.handleCreateView(w, r, filerClient)
case "GetView":
err = h.handleGetView(w, r, filerClient)
case "ListViews":
err = h.handleListViews(w, r, filerClient)
case "UpdateView":
err = h.handleUpdateView(w, r, filerClient)
case "DeleteView":
err = h.handleDeleteView(w, r, filerClient)
// Table Policy operations
case "PutTablePolicy":
err = h.handlePutTablePolicy(w, r, filerClient)
@@ -363,6 +381,10 @@ func (h *S3TablesHandler) generateTableARN(ownerAccountID, bucketName, tableID s
return fmt.Sprintf("arn:aws:s3tables:%s:%s:bucket/%s/table/%s", h.region, ownerAccountID, bucketName, tableID)
}
func (h *S3TablesHandler) generateViewARN(ownerAccountID, bucketName, viewID string) string {
return fmt.Sprintf("arn:aws:s3tables:%s:%s:bucket/%s/view/%s", h.region, ownerAccountID, bucketName, viewID)
}
func isAuthError(err error) bool {
var authErr *AuthError
return errors.As(err, &authErr) || errors.Is(err, ErrAccessDenied)