mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-08-18 21:26:56 +00:00
s3tables: allow hyphens in namespace and table names (#10093)
* s3tables: allow hyphens in namespace and table names Iceberg REST clients routinely use hyphenated namespace/table names, but the S3 Tables charset (a-z, 0-9, _) rejected them with 400. Accept '-' as an interior character (names must still start, and namespaces end, with a letter or digit), making the catalog conformant for those clients. A permissive superset of the AWS S3 Tables charset. * s3tables: allow hyphens in table ARN parsing too The ARN regexes still excluded '-', so parseTableFromARN rejected ARNs with hyphenated namespace/table names and existing reject-the-hyphen tests broke. Widen the ARN patterns to match the validator, retarget those tests at a still-invalid leading-hyphen name, and cover ARN parsing with hyphens.
This commit is contained in:
@@ -21,7 +21,7 @@ func TestBuildListTablesRequestRejectsInvalidNamespaceQuery(t *testing.T) {
|
||||
namespace string
|
||||
}{
|
||||
{name: "uppercase", namespace: "InvalidNamespace"},
|
||||
{name: "hyphen", namespace: "invalid-ns"},
|
||||
{name: "leading hyphen", namespace: "-invalid"},
|
||||
{name: "slash", namespace: "a/b"},
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ func TestBuildGetTableRequestRejectsInvalidNamespaceQuery(t *testing.T) {
|
||||
namespace string
|
||||
}{
|
||||
{name: "uppercase", namespace: "InvalidNamespace"},
|
||||
{name: "hyphen", namespace: "invalid-ns"},
|
||||
{name: "leading hyphen", namespace: "-invalid"},
|
||||
{name: "slash", namespace: "a/b"},
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ func TestHandleRestOperationReturnsBadRequestForInvalidNamespaceQuery(t *testing
|
||||
namespace string
|
||||
}{
|
||||
{name: "uppercase", namespace: "InvalidNamespace"},
|
||||
{name: "hyphen", namespace: "invalid-ns"},
|
||||
{name: "leading hyphen", namespace: "-invalid"},
|
||||
{name: "slash", namespace: "a/b"},
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package s3tables
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNameValidationAllowsHyphens(t *testing.T) {
|
||||
// Hyphen interior is accepted (Iceberg REST clients use names like these).
|
||||
require.NoError(t, validateNamespacePart("rest-integration-test"))
|
||||
if _, err := validateTableName("test-create-table"); err != nil {
|
||||
t.Fatalf("hyphenated table name rejected: %v", err)
|
||||
}
|
||||
|
||||
// A leading or trailing hyphen on a namespace is still rejected (must start
|
||||
// and end with a letter or digit), as are characters outside the charset.
|
||||
require.Error(t, validateNamespacePart("-leading"))
|
||||
require.Error(t, validateNamespacePart("trailing-"))
|
||||
require.Error(t, validateNamespacePart("Upper"))
|
||||
|
||||
// Table names must still start with a letter or digit and stay in charset.
|
||||
if _, err := validateTableName("-leading"); err == nil {
|
||||
t.Fatal("table name with leading hyphen should be rejected")
|
||||
}
|
||||
if _, err := validateTableName("Upper"); err == nil {
|
||||
t.Fatal("uppercase table name should be rejected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseTableFromARNAllowsHyphens(t *testing.T) {
|
||||
bucket, ns, table, err := parseTableFromARN("arn:aws:s3tables:us-east-1:123456789012:bucket/my-bucket/table/rest-integration-test/test-create-table")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "my-bucket", bucket)
|
||||
require.Equal(t, "rest-integration-test", ns)
|
||||
require.Equal(t, "test-create-table", table)
|
||||
}
|
||||
@@ -15,8 +15,8 @@ import (
|
||||
|
||||
const (
|
||||
bucketNamePatternStr = `[a-z0-9-]+`
|
||||
tableNamespacePatternStr = `[a-z0-9_.]+`
|
||||
tableNamePatternStr = `[a-z0-9_]+`
|
||||
tableNamespacePatternStr = `[a-z0-9_.-]+`
|
||||
tableNamePatternStr = `[a-z0-9_-]+`
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -344,12 +344,12 @@ func validateNamespacePart(name string) error {
|
||||
return fmt.Errorf("namespace name must end with a letter or digit")
|
||||
}
|
||||
|
||||
// Allowed characters: a-z, 0-9, _
|
||||
// Allowed characters: a-z, 0-9, _, - (hyphen interior; start/end checked above)
|
||||
for _, ch := range name {
|
||||
if (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '_' {
|
||||
if (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '_' || ch == '-' {
|
||||
continue
|
||||
}
|
||||
return fmt.Errorf("invalid namespace name: only 'a-z', '0-9', and '_' are allowed")
|
||||
return fmt.Errorf("invalid namespace name: only 'a-z', '0-9', '_', and '-' are allowed")
|
||||
}
|
||||
|
||||
// Reserved prefix
|
||||
@@ -411,12 +411,12 @@ func validateTableName(name string) (string, error) {
|
||||
return "", fmt.Errorf("table name must start with a letter or digit")
|
||||
}
|
||||
|
||||
// Allowed characters: a-z, 0-9, _
|
||||
// Allowed characters: a-z, 0-9, _, - (start checked above)
|
||||
for _, ch := range name {
|
||||
if (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '_' {
|
||||
if (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '_' || ch == '-' {
|
||||
continue
|
||||
}
|
||||
return "", fmt.Errorf("invalid table name: only 'a-z', '0-9', and '_' are allowed")
|
||||
return "", fmt.Errorf("invalid table name: only 'a-z', '0-9', '_', and '-' are allowed")
|
||||
}
|
||||
return name, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user