mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-09-20 13:30:46 +02:00
* s3api/iceberg: report the reason a table schema was rejected newTableMetadata swallowed the iceberg-go error and returned nil, so every schema the metadata builder refused came back as a bare 500 "Failed to build table metadata". A v3-only column type is the common case: creating a table with a variant field but no format-version 3 property leaves the client with nothing, while "variant is not supported until v3" sits in the server log. Return the error instead and classify it. Schema, spec and argument failures are the caller's input, so they answer 400 with the underlying reason; the rest stay 500. Paths that build placeholder metadata with no schema keep their existing 500 via newEmptyTableMetadata. * s3api/iceberg: fail LoadTable when placeholder metadata cannot be built buildLoadTableResult dropped a nil from the placeholder path straight into the response. That serializes as "metadata":null under HTTP 200, which no Iceberg client can parse -- a worse outcome than the 500 the nil was meant to signal. Return an error instead and let the five callers answer 500. The nil-return convention goes away with it, so the commit and transaction paths check an error rather than a sentinel. * s3api/iceberg: route rejected schemas through writeManagerError The two helpers added here duplicated work the package already does. writeManagerError is the canonical error-to-response mapper -- it already downgrades client-input failures to 400 and defaults the rest to 500 -- so teach it the iceberg-go schema and spec sentinels instead of standing up a parallel classifier. The placeholder wrapper was a pure alias for newTableMetadata with nil arguments; call that directly. No behavior change beyond the 500 message, which now reads err.Error() like every other manager error rather than carrying its own prefix.
109 lines
3.7 KiB
Go
109 lines
3.7 KiB
Go
package iceberg
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/apache/iceberg-go"
|
|
"github.com/google/uuid"
|
|
"github.com/seaweedfs/seaweedfs/weed/s3api/s3tables"
|
|
)
|
|
|
|
func TestValidateCreateTableRequestRequiresName(t *testing.T) {
|
|
err := validateCreateTableRequest(CreateTableRequest{})
|
|
if !errors.Is(err, errTableNameRequired) {
|
|
t.Fatalf("validateCreateTableRequest() error = %v, want errTableNameRequired", err)
|
|
}
|
|
}
|
|
|
|
func TestValidateCreateTableRequestAcceptsWithName(t *testing.T) {
|
|
err := validateCreateTableRequest(CreateTableRequest{Name: "orders"})
|
|
if err != nil {
|
|
t.Fatalf("validateCreateTableRequest() error = %v, want nil", err)
|
|
}
|
|
}
|
|
|
|
func TestIsStageCreateEnabledDefaultsToTrue(t *testing.T) {
|
|
t.Setenv("ICEBERG_ENABLE_STAGE_CREATE", "")
|
|
if !isStageCreateEnabled() {
|
|
t.Fatalf("isStageCreateEnabled() = false, want true")
|
|
}
|
|
}
|
|
|
|
func TestIsStageCreateEnabledFalseValues(t *testing.T) {
|
|
falseValues := []string{"0", "false", "FALSE", "no", "off"}
|
|
for _, value := range falseValues {
|
|
t.Setenv("ICEBERG_ENABLE_STAGE_CREATE", value)
|
|
if isStageCreateEnabled() {
|
|
t.Fatalf("isStageCreateEnabled() = true for value %q, want false", value)
|
|
}
|
|
}
|
|
}
|
|
|
|
func mustParseSchema(t *testing.T, raw string) *iceberg.Schema {
|
|
t.Helper()
|
|
var schema iceberg.Schema
|
|
if err := json.Unmarshal([]byte(raw), &schema); err != nil {
|
|
t.Fatalf("parse schema: %v", err)
|
|
}
|
|
return &schema
|
|
}
|
|
|
|
const variantSchema = `{"type":"struct","schema-id":0,"fields":[
|
|
{"id":1,"name":"id","required":true,"type":"long"},
|
|
{"id":2,"name":"payload","required":false,"type":"variant"}]}`
|
|
|
|
// A v3-only column type without format-version 3 is the client's mistake, and
|
|
// the reason has to travel back to them rather than only into the server log.
|
|
func TestNewTableMetadataRejectsV3TypeBelowV3(t *testing.T) {
|
|
_, err := newTableMetadata(uuid.New(), "s3://bkt/ns/t", mustParseSchema(t, variantSchema), nil, nil, nil)
|
|
if err == nil {
|
|
t.Fatal("newTableMetadata() error = nil, want invalid schema")
|
|
}
|
|
if !errors.Is(err, iceberg.ErrInvalidSchema) {
|
|
t.Fatalf("newTableMetadata() error = %v, want ErrInvalidSchema", err)
|
|
}
|
|
|
|
// writeManagerError turns this into a 400; see TestWriteManagerError.
|
|
if !strings.Contains(err.Error(), "variant is not supported until v3") {
|
|
t.Errorf("error = %q, want the underlying reason", err.Error())
|
|
}
|
|
}
|
|
|
|
func TestNewTableMetadataAcceptsV3TypeAtV3(t *testing.T) {
|
|
metadata, err := newTableMetadata(uuid.New(), "s3://bkt/ns/t", mustParseSchema(t, variantSchema), nil, nil,
|
|
iceberg.Properties{"format-version": "3"})
|
|
if err != nil {
|
|
t.Fatalf("newTableMetadata() error = %v, want nil", err)
|
|
}
|
|
if got := metadata.Version(); got != 3 {
|
|
t.Fatalf("metadata.Version() = %d, want 3", got)
|
|
}
|
|
if _, found := metadata.CurrentSchema().FindFieldByName("payload"); !found {
|
|
t.Error("variant field missing from stored schema")
|
|
}
|
|
}
|
|
|
|
// A LoadTable response must never carry nil metadata: it serializes as
|
|
// "metadata":null under HTTP 200, which no Iceberg client can parse.
|
|
func TestBuildLoadTableResultNeverReturnsNilMetadata(t *testing.T) {
|
|
cases := map[string]s3tables.GetTableResponse{
|
|
"no stored metadata": {MetadataLocation: "s3://bkt/ns/t/metadata/v1.metadata.json"},
|
|
"empty full metadata": {Metadata: &s3tables.TableMetadata{}},
|
|
"unparseable metadata": {Metadata: &s3tables.TableMetadata{FullMetadata: json.RawMessage(`{"nope":`)}},
|
|
}
|
|
for name, getResp := range cases {
|
|
t.Run(name, func(t *testing.T) {
|
|
result, err := (&Server{}).buildLoadTableResult(getResp, "bkt", []string{"ns"}, "t")
|
|
if err != nil {
|
|
t.Fatalf("buildLoadTableResult() error = %v, want nil", err)
|
|
}
|
|
if result.Metadata == nil {
|
|
t.Fatal("buildLoadTableResult() returned nil metadata with no error")
|
|
}
|
|
})
|
|
}
|
|
}
|