iceberg: persist namespace properties for create/get (#8276)
* iceberg: persist namespace properties via s3tables metadata * iceberg: simplify namespace properties normalization * s3tables: broaden namespace properties round-trip test * adjust logs * adjust logs
This commit is contained in:
@@ -147,6 +147,7 @@ func (h *S3TablesHandler) handleCreateNamespace(w http.ResponseWriter, r *http.R
|
||||
Namespace: req.Namespace,
|
||||
CreatedAt: now,
|
||||
OwnerAccountID: bucketMetadata.OwnerAccountID,
|
||||
Properties: req.Properties,
|
||||
}
|
||||
|
||||
metadataBytes, err := json.Marshal(metadata)
|
||||
@@ -177,6 +178,7 @@ func (h *S3TablesHandler) handleCreateNamespace(w http.ResponseWriter, r *http.R
|
||||
resp := &CreateNamespaceResponse{
|
||||
Namespace: req.Namespace,
|
||||
TableBucketARN: req.TableBucketARN,
|
||||
Properties: req.Properties,
|
||||
}
|
||||
|
||||
h.writeJSON(w, http.StatusOK, resp)
|
||||
@@ -265,6 +267,7 @@ func (h *S3TablesHandler) handleGetNamespace(w http.ResponseWriter, r *http.Requ
|
||||
Namespace: metadata.Namespace,
|
||||
CreatedAt: metadata.CreatedAt,
|
||||
OwnerAccountID: metadata.OwnerAccountID,
|
||||
Properties: metadata.Properties,
|
||||
}
|
||||
|
||||
h.writeJSON(w, http.StatusOK, resp)
|
||||
|
||||
@@ -77,19 +77,22 @@ type DeleteTableBucketPolicyRequest struct {
|
||||
// Namespace types
|
||||
|
||||
type Namespace struct {
|
||||
Namespace []string `json:"namespace"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
OwnerAccountID string `json:"ownerAccountId"`
|
||||
Namespace []string `json:"namespace"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
OwnerAccountID string `json:"ownerAccountId"`
|
||||
Properties map[string]string `json:"properties,omitempty"`
|
||||
}
|
||||
|
||||
type CreateNamespaceRequest struct {
|
||||
TableBucketARN string `json:"tableBucketARN"`
|
||||
Namespace []string `json:"namespace"`
|
||||
TableBucketARN string `json:"tableBucketARN"`
|
||||
Namespace []string `json:"namespace"`
|
||||
Properties map[string]string `json:"properties,omitempty"`
|
||||
}
|
||||
|
||||
type CreateNamespaceResponse struct {
|
||||
Namespace []string `json:"namespace"`
|
||||
TableBucketARN string `json:"tableBucketARN"`
|
||||
Namespace []string `json:"namespace"`
|
||||
TableBucketARN string `json:"tableBucketARN"`
|
||||
Properties map[string]string `json:"properties,omitempty"`
|
||||
}
|
||||
|
||||
type GetNamespaceRequest struct {
|
||||
@@ -98,9 +101,10 @@ type GetNamespaceRequest struct {
|
||||
}
|
||||
|
||||
type GetNamespaceResponse struct {
|
||||
Namespace []string `json:"namespace"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
OwnerAccountID string `json:"ownerAccountId"`
|
||||
Namespace []string `json:"namespace"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
OwnerAccountID string `json:"ownerAccountId"`
|
||||
Properties map[string]string `json:"properties,omitempty"`
|
||||
}
|
||||
|
||||
type ListNamespacesRequest struct {
|
||||
|
||||
@@ -128,9 +128,10 @@ type tableBucketMetadata struct {
|
||||
|
||||
// namespaceMetadata stores metadata for a namespace
|
||||
type namespaceMetadata struct {
|
||||
Namespace []string `json:"namespace"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
OwnerAccountID string `json:"ownerAccountId"`
|
||||
Namespace []string `json:"namespace"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
OwnerAccountID string `json:"ownerAccountId"`
|
||||
Properties map[string]string `json:"properties,omitempty"`
|
||||
}
|
||||
|
||||
// tableMetadataInternal stores metadata for a table
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package s3tables
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
@@ -124,3 +126,61 @@ func TestExpandNamespace(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNamespaceMetadataPropertiesRoundTrip(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
metadata namespaceMetadata
|
||||
}{
|
||||
{
|
||||
name: "with properties",
|
||||
metadata: namespaceMetadata{
|
||||
Namespace: []string{"analytics"},
|
||||
Properties: map[string]string{"owner": "finance"},
|
||||
OwnerAccountID: "123456789012",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nil properties",
|
||||
metadata: namespaceMetadata{
|
||||
Namespace: []string{"analytics"},
|
||||
Properties: nil,
|
||||
OwnerAccountID: "123456789012",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty properties",
|
||||
metadata: namespaceMetadata{
|
||||
Namespace: []string{"analytics"},
|
||||
Properties: map[string]string{},
|
||||
OwnerAccountID: "123456789012",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
data, err := json.Marshal(tc.metadata)
|
||||
if err != nil {
|
||||
t.Fatalf("json.Marshal(metadata) returned error: %v", err)
|
||||
}
|
||||
|
||||
var decoded namespaceMetadata
|
||||
if err := json.Unmarshal(data, &decoded); err != nil {
|
||||
t.Fatalf("json.Unmarshal(data) returned error: %v", err)
|
||||
}
|
||||
|
||||
// Due to `omitempty`, nil and empty maps are unmarshaled as nil.
|
||||
if len(tc.metadata.Properties) == 0 {
|
||||
if decoded.Properties != nil {
|
||||
t.Fatalf("expected nil properties for empty/nil input, got %v", decoded.Properties)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(decoded.Properties, tc.metadata.Properties) {
|
||||
t.Fatalf("decoded.Properties = %v, want %v", decoded.Properties, tc.metadata.Properties)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user