mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-21 13:34:26 +02:00
test(s3tables): expand Dremio Iceberg catalog test coverage (#9303)
* test(s3tables): expand Dremio Iceberg catalog test coverage
Restructure TestDremioIcebergCatalog into subtests and add three new
checks that go beyond a connectivity smoke test:
- ColumnProjection: SELECT id, label proves Dremio parsed the schema
served by the SeaweedFS REST catalog (the previous SELECT COUNT(*)
passed without exercising any column metadata).
- InformationSchemaColumns: verifies the table's columns are listed in
Dremio's INFORMATION_SCHEMA.COLUMNS in the expected ordinal order.
- InformationSchemaTables: verifies the table is registered in
INFORMATION_SCHEMA.TABLES.
All subtests share a single Dremio container startup, so total
runtime is unchanged.
* test(s3tables): exercise multi-level Iceberg namespaces from Dremio
Seed a 2-level Iceberg namespace (and a table inside it) via the REST
catalog before bootstrapping Dremio, then add a MultiLevelNamespace
subtest that scans the nested table by its dot-separated reference.
This relies on isRecursiveAllowedNamespaces=true (already set in the
Dremio source config) to surface the nested levels as folders. A
regression in either the SeaweedFS namespace path encoding (#8959-style)
or Dremio's recursive-namespace discovery would surface here.
Adds two helpers to keep the existing single-level call sites unchanged:
- createIcebergNamespaceLevels: namespace creation with []string levels
- createIcebergTableInLevels: table creation with []string levels and
unit-separator (0x1F) URL encoding for the namespace path component
* test(s3tables): verify Dremio reads PyIceberg-written rows
The previous Dremio subtests only scanned empty tables, so they did not
exercise the data path - just the catalog/metadata path. Add a
PyIceberg-based writer that materializes parquet files plus a snapshot
on a separate table before Dremio bootstraps, and two new subtests:
- ReadWrittenDataCount: SELECT COUNT(*) returns 3.
- ReadWrittenDataValues: SELECT id, label ORDER BY id returns the three
written rows with the expected (id, label) pairs.
The writer runs in a small image (Dockerfile.writer) built locally on
demand. It pip-installs pyiceberg+pyarrow once and reuses the layer
cache on subsequent runs. The CI workflow pre-pulls python:3.11-slim
to keep cold runs predictable.
The writer authenticates via the OAuth2 client_credentials flow that
SeaweedFS already exposes at /v1/oauth/tokens, mirroring the Go-side
helper used for REST-API table creation.
* test(s3tables): fix Dremio writer required-field schema mismatch
PyIceberg's append() compatibility check rejects an arrow column whose
nullability does not match the Iceberg field. The table schema declares
id as `required long`, but the default pyarrow int64 column is nullable
- so the writer failed with:
1: id: required long vs. 1: id: optional long
Declare an explicit pyarrow schema with nullable=False on id and
nullable=True on label to match the Iceberg side.
This commit is contained in:
1 parent
f16353de0b
commit
fc75f16c30
5 files changed
+380
-14
No files matched your search
@@ -0,0 +1,87 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Append rows to an existing Iceberg table via the SeaweedFS REST catalog.
|
||||
|
||||
Used by the Dremio integration test to materialize data files so a downstream
|
||||
SELECT from Dremio verifies the read path against non-empty results.
|
||||
|
||||
Usage:
|
||||
python3 append_rows.py \\
|
||||
--catalog-url http://localhost:8181 \\
|
||||
--warehouse s3://my-bucket \\
|
||||
--prefix my-bucket \\
|
||||
--s3-endpoint http://localhost:8333 \\
|
||||
--access-key AKIA... --secret-key wJalr... \\
|
||||
--namespace foo --namespace bar \\
|
||||
--table events
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
import pyarrow as pa
|
||||
from pyiceberg.catalog import load_catalog
|
||||
|
||||
|
||||
def main() -> int:
|
||||
p = argparse.ArgumentParser()
|
||||
p.add_argument("--catalog-url", required=True)
|
||||
p.add_argument("--warehouse", required=True, help="s3://<bucket-name>")
|
||||
p.add_argument("--prefix", required=True, help="REST catalog prefix (table bucket name)")
|
||||
p.add_argument("--s3-endpoint", required=True, help="http://host:port")
|
||||
p.add_argument("--access-key", required=True)
|
||||
p.add_argument("--secret-key", required=True)
|
||||
p.add_argument("--region", default="us-east-1")
|
||||
p.add_argument(
|
||||
"--namespace",
|
||||
action="append",
|
||||
required=True,
|
||||
help="One per level (e.g. --namespace foo --namespace bar for foo.bar).",
|
||||
)
|
||||
p.add_argument("--table", required=True)
|
||||
args = p.parse_args()
|
||||
|
||||
# `credential` triggers OAuth2 client_credentials against
|
||||
# <catalog_uri>/v1/oauth/tokens, matching the helper Go test uses for
|
||||
# REST-API table creation. The s3.* keys are needed for parquet writes.
|
||||
catalog = load_catalog(
|
||||
"rest",
|
||||
**{
|
||||
"type": "rest",
|
||||
"uri": args.catalog_url,
|
||||
"warehouse": args.warehouse,
|
||||
"prefix": args.prefix,
|
||||
"credential": f"{args.access_key}:{args.secret_key}",
|
||||
"s3.access-key-id": args.access_key,
|
||||
"s3.secret-access-key": args.secret_key,
|
||||
"s3.endpoint": args.s3_endpoint,
|
||||
"s3.region": args.region,
|
||||
"s3.path-style-access": "true",
|
||||
},
|
||||
)
|
||||
|
||||
table_id = tuple(args.namespace) + (args.table,)
|
||||
table = catalog.load_table(table_id)
|
||||
|
||||
# Match the Iceberg table schema: id is `required long`, label is
|
||||
# `optional string`. Default pyarrow columns are nullable, which fails
|
||||
# PyIceberg's required-field compatibility check.
|
||||
arrow_schema = pa.schema(
|
||||
[
|
||||
pa.field("id", pa.int64(), nullable=False),
|
||||
pa.field("label", pa.string(), nullable=True),
|
||||
]
|
||||
)
|
||||
arrow_table = pa.Table.from_pydict(
|
||||
{
|
||||
"id": [1, 2, 3],
|
||||
"label": ["one", "two", "three"],
|
||||
},
|
||||
schema=arrow_schema,
|
||||
)
|
||||
table.append(arrow_table)
|
||||
print(f"appended {arrow_table.num_rows} rows to {'.'.join(table_id)}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in new issue
Block a user