laskoviymishka commented on code in PR #1244:
URL: https://github.com/apache/iceberg-go/pull/1244#discussion_r3455856277
##########
catalog/sql/sql.go:
##########
@@ -247,7 +247,76 @@ func (c *Catalog) DropSQLTables(ctx context.Context) error
{
}
func (c *Catalog) ensureTablesExist() error {
- return c.CreateSQLTables(context.Background())
+ ctx := context.Background()
+ if err := c.CreateSQLTables(ctx); err != nil {
+ return err
+ }
+
+ return c.migrateV0Schema(ctx)
+}
+
+func (c *Catalog) migrateV0Schema(ctx context.Context) error {
+ hasCol, probeErr := c.icebergTypeColumnExists(ctx)
+ if probeErr != nil {
+ log.Printf("WARNING: skipping V0 schema migration; iceberg_type
column probe failed: %v", probeErr)
+
+ return nil
+ }
+ if hasCol {
+ return nil
+ }
+
+ if _, err := c.db.ExecContext(ctx, "ALTER TABLE iceberg_tables ADD
COLUMN iceberg_type VARCHAR(5)"); err != nil {
+ has, reprobeErr := c.icebergTypeColumnExists(ctx)
+ if has {
+ return nil
+ }
+ if reprobeErr != nil {
+ log.Printf("WARNING: V0 schema migration re-probe
failed after ALTER error: %v", reprobeErr)
+ }
+
+ return fmt.Errorf("migration of V0 schema failed: %w", err)
+ }
+
+ return nil
+}
+
+func (c *Catalog) icebergTypeColumnExists(ctx context.Context) (bool, error) {
+ var query string
+ switch dialectName := c.db.Dialect().Name().String(); dialectName {
+ case "pg":
Review Comment:
The Postgres probe doesn't scope to the current schema, and the `mssql` case
below has the same gap. `information_schema.columns` spans every schema the
user can see, so if another schema has an `iceberg_tables` that already carries
`iceberg_type`, this returns true, we skip the migration, and the first real
query against our table fails. Multi-schema Postgres is common, and the JDBC
catalog supports a configurable schema. I'd add `AND table_schema =
current_schema()` here and `AND table_schema = SCHEMA_NAME()` to the MSSQL case
— MySQL already gets this right with `table_schema = DATABASE()`.
##########
catalog/sql/sql.go:
##########
@@ -247,7 +247,76 @@ func (c *Catalog) DropSQLTables(ctx context.Context) error
{
}
func (c *Catalog) ensureTablesExist() error {
- return c.CreateSQLTables(context.Background())
+ ctx := context.Background()
+ if err := c.CreateSQLTables(ctx); err != nil {
+ return err
+ }
+
+ return c.migrateV0Schema(ctx)
+}
+
+func (c *Catalog) migrateV0Schema(ctx context.Context) error {
Review Comment:
This is the piece I'd most want to settle before merge. The migration runs
on every open where `init_catalog_tables` is set (the default), so we issue
one-way DDL against what may be a catalog shared with Java or PyIceberg, with
no opt-out.
Java made this an explicit choice — `JdbcCatalog` only migrates when
`jdbc.schema-version=V1` is set, otherwise it warns and stays on V0. I'd mirror
that: take a `jdbc.schema-version` property (same key as Java, for interop),
run the ALTER only when it's `V1`, and otherwise lean on the NULL-tolerant read
path this PR already adds. There's also no exported constant for that knob the
way `init_catalog_tables` has one. wdyt?
##########
catalog/sql/sql.go:
##########
@@ -247,7 +247,76 @@ func (c *Catalog) DropSQLTables(ctx context.Context) error
{
}
func (c *Catalog) ensureTablesExist() error {
- return c.CreateSQLTables(context.Background())
+ ctx := context.Background()
+ if err := c.CreateSQLTables(ctx); err != nil {
+ return err
+ }
+
+ return c.migrateV0Schema(ctx)
+}
+
+func (c *Catalog) migrateV0Schema(ctx context.Context) error {
+ hasCol, probeErr := c.icebergTypeColumnExists(ctx)
+ if probeErr != nil {
Review Comment:
Swallowing the probe error and returning nil means `NewCatalog` hands back a
catalog that looks fine, then the first table op fails with a cryptic `no such
column: iceberg_type` far from the real cause. The `default` case in the probe
gets silently no-op'd the same way. I'd return the wrapped error here
(`fmt.Errorf("V0 schema migration: column probe failed: %w", probeErr)`) so we
fail fast at construction; if the goal is to let read-only users who can't
probe still connect, I'd make that an explicit option rather than the silent
default.
(Minor, while here: `log.Printf` writes to the default logger and bypasses
whatever logging the caller configured — consistent with `PurgeTable` today,
but worth keeping in mind.)
##########
catalog/sql/sql.go:
##########
@@ -247,7 +247,76 @@ func (c *Catalog) DropSQLTables(ctx context.Context) error
{
}
func (c *Catalog) ensureTablesExist() error {
- return c.CreateSQLTables(context.Background())
+ ctx := context.Background()
+ if err := c.CreateSQLTables(ctx); err != nil {
+ return err
+ }
+
+ return c.migrateV0Schema(ctx)
+}
+
+func (c *Catalog) migrateV0Schema(ctx context.Context) error {
+ hasCol, probeErr := c.icebergTypeColumnExists(ctx)
+ if probeErr != nil {
+ log.Printf("WARNING: skipping V0 schema migration; iceberg_type
column probe failed: %v", probeErr)
+
+ return nil
+ }
+ if hasCol {
+ return nil
+ }
+
+ if _, err := c.db.ExecContext(ctx, "ALTER TABLE iceberg_tables ADD
COLUMN iceberg_type VARCHAR(5)"); err != nil {
+ has, reprobeErr := c.icebergTypeColumnExists(ctx)
+ if has {
+ return nil
+ }
+ if reprobeErr != nil {
+ log.Printf("WARNING: V0 schema migration re-probe
failed after ALTER error: %v", reprobeErr)
+ }
+
+ return fmt.Errorf("migration of V0 schema failed: %w", err)
+ }
+
+ return nil
+}
+
+func (c *Catalog) icebergTypeColumnExists(ctx context.Context) (bool, error) {
+ var query string
+ switch dialectName := c.db.Dialect().Name().String(); dialectName {
+ case "pg":
+ query = `SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'iceberg_tables'
+ AND column_name = 'iceberg_type' LIMIT 1`
+ case "mysql":
+ query = `SELECT 1 FROM information_schema.columns
+ WHERE table_schema = DATABASE()
+ AND table_name = 'iceberg_tables'
+ AND column_name = 'iceberg_type' LIMIT 1`
+ case "mssql":
+ query = `SELECT TOP 1 1 FROM information_schema.columns
+ WHERE table_name = 'iceberg_tables'
+ AND column_name = 'iceberg_type'`
+ case "sqlite":
+ query = `SELECT 1 FROM pragma_table_info('iceberg_tables')
+ WHERE name = 'iceberg_type' LIMIT 1`
+ case "oracle":
+ query = `SELECT 1 FROM user_tab_columns
+ WHERE UPPER(table_name) = 'ICEBERG_TABLES'
+ AND UPPER(column_name) = 'ICEBERG_TYPE'`
+ default:
+ return false, fmt.Errorf("unsupported dialect for V0 migration:
%s", dialectName)
+ }
+ var dummy int
Review Comment:
`dummy` undersells it — the scanned value is a real existence sentinel. `var
exists int` reads better, or skip the scan target entirely by probing with
`COUNT(*)` into an int and checking `> 0`.
##########
catalog/sql/sql.go:
##########
@@ -247,7 +247,76 @@ func (c *Catalog) DropSQLTables(ctx context.Context) error
{
}
func (c *Catalog) ensureTablesExist() error {
- return c.CreateSQLTables(context.Background())
+ ctx := context.Background()
+ if err := c.CreateSQLTables(ctx); err != nil {
+ return err
+ }
+
+ return c.migrateV0Schema(ctx)
+}
+
+func (c *Catalog) migrateV0Schema(ctx context.Context) error {
+ hasCol, probeErr := c.icebergTypeColumnExists(ctx)
+ if probeErr != nil {
+ log.Printf("WARNING: skipping V0 schema migration; iceberg_type
column probe failed: %v", probeErr)
+
+ return nil
+ }
+ if hasCol {
+ return nil
+ }
+
+ if _, err := c.db.ExecContext(ctx, "ALTER TABLE iceberg_tables ADD
COLUMN iceberg_type VARCHAR(5)"); err != nil {
+ has, reprobeErr := c.icebergTypeColumnExists(ctx)
+ if has {
+ return nil
+ }
+ if reprobeErr != nil {
+ log.Printf("WARNING: V0 schema migration re-probe
failed after ALTER error: %v", reprobeErr)
+ }
+
+ return fmt.Errorf("migration of V0 schema failed: %w", err)
+ }
+
+ return nil
+}
+
+func (c *Catalog) icebergTypeColumnExists(ctx context.Context) (bool, error) {
+ var query string
+ switch dialectName := c.db.Dialect().Name().String(); dialectName {
Review Comment:
Small robustness thing: switching on `c.db.Dialect().Name().String()`
compares against bun's internal strings and leaks one back out in the `default`
error (e.g. `pg`, not the public `postgres`). bun exports the typed
`dialect.Name` constants (`dialect.PG`, `dialect.SQLite`, …) — switching on
those instead is sturdier against name drift and lets the `exhaustive` linter
actually check this switch.
##########
catalog/sql/sql.go:
##########
@@ -349,11 +418,11 @@ func (c *Catalog) CommitTable(ctx context.Context, ident
table.Identifier, reqs
CatalogName: c.name,
TableNamespace: strings.Join(ns, "."),
TableName: tblName,
- IcebergType: TableType,
+ IcebergType:
sql.NullString{String: TableType, Valid: true},
Review Comment:
Heads up — because this UPDATE is driven off the model struct, bun puts
every non-PK field in the SET clause, so we write `iceberg_type = 'TABLE'` back
onto the row on every commit. Java's V1 commit only sets
`metadata_location`/`previous_metadata_location` and leaves `iceberg_type`
alone (it's WHERE-only there). It's harmless — NULL and `'TABLE'` read
identically through the new predicate — but it's an intentional-looking
divergence the test asserts as correct, so either a one-line comment saying we
opportunistically heal V0 rows on commit, or explicit `Set(...)` calls to match
Java.
##########
catalog/sql/sql.go:
##########
@@ -247,7 +247,76 @@ func (c *Catalog) DropSQLTables(ctx context.Context) error
{
}
func (c *Catalog) ensureTablesExist() error {
- return c.CreateSQLTables(context.Background())
+ ctx := context.Background()
+ if err := c.CreateSQLTables(ctx); err != nil {
+ return err
+ }
+
+ return c.migrateV0Schema(ctx)
+}
+
+func (c *Catalog) migrateV0Schema(ctx context.Context) error {
+ hasCol, probeErr := c.icebergTypeColumnExists(ctx)
+ if probeErr != nil {
+ log.Printf("WARNING: skipping V0 schema migration; iceberg_type
column probe failed: %v", probeErr)
+
+ return nil
+ }
+ if hasCol {
+ return nil
+ }
+
+ if _, err := c.db.ExecContext(ctx, "ALTER TABLE iceberg_tables ADD
COLUMN iceberg_type VARCHAR(5)"); err != nil {
+ has, reprobeErr := c.icebergTypeColumnExists(ctx)
+ if has {
+ return nil
+ }
+ if reprobeErr != nil {
+ log.Printf("WARNING: V0 schema migration re-probe
failed after ALTER error: %v", reprobeErr)
+ }
+
+ return fmt.Errorf("migration of V0 schema failed: %w", err)
+ }
+
+ return nil
+}
+
+func (c *Catalog) icebergTypeColumnExists(ctx context.Context) (bool, error) {
+ var query string
+ switch dialectName := c.db.Dialect().Name().String(); dialectName {
+ case "pg":
+ query = `SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'iceberg_tables'
+ AND column_name = 'iceberg_type' LIMIT 1`
+ case "mysql":
+ query = `SELECT 1 FROM information_schema.columns
+ WHERE table_schema = DATABASE()
+ AND table_name = 'iceberg_tables'
+ AND column_name = 'iceberg_type' LIMIT 1`
+ case "mssql":
+ query = `SELECT TOP 1 1 FROM information_schema.columns
+ WHERE table_name = 'iceberg_tables'
+ AND column_name = 'iceberg_type'`
+ case "sqlite":
+ query = `SELECT 1 FROM pragma_table_info('iceberg_tables')
+ WHERE name = 'iceberg_type' LIMIT 1`
+ case "oracle":
+ query = `SELECT 1 FROM user_tab_columns
+ WHERE UPPER(table_name) = 'ICEBERG_TABLES'
+ AND UPPER(column_name) = 'ICEBERG_TYPE'`
+ default:
+ return false, fmt.Errorf("unsupported dialect for V0 migration:
%s", dialectName)
+ }
+ var dummy int
+ if err := c.db.QueryRowContext(ctx, query).Scan(&dummy); err != nil {
+ if errors.Is(err, sql.ErrNoRows) {
+ return false, nil
+ }
+
+ return false, err
Review Comment:
Bare `err` here — the rest of the file wraps with `fmt.Errorf("...: %w",
err)`. Worth wrapping so the caller (and the warning log) gets some context
about what failed.
##########
catalog/sql/sql_test.go:
##########
@@ -322,6 +322,182 @@ func (s *SqliteCatalogTestSuite)
TestCreationAllTablesExist() {
s.confirmTablesExist(sqldb)
}
+func (s *SqliteCatalogTestSuite) TestV0SchemaFromIcebergIsMigrated() {
+ sqldb := s.getDB()
+ s.confirmNoTables(sqldb)
+
+ _, err := sqldb.Exec(`CREATE TABLE "iceberg_tables" (
+ "catalog_name" VARCHAR NOT NULL,
+ "table_namespace" VARCHAR NOT NULL,
+ "table_name" VARCHAR NOT NULL,
+ "metadata_location" VARCHAR,
+ "previous_metadata_location" VARCHAR,
+ PRIMARY KEY ("catalog_name", "table_namespace", "table_name"))`)
+ s.Require().NoError(err)
+ _, err = sqldb.Exec(`CREATE TABLE "iceberg_namespace_properties" (
+ "catalog_name" VARCHAR NOT NULL,
+ "namespace" VARCHAR NOT NULL,
+ "property_key" VARCHAR NOT NULL,
+ "property_value" VARCHAR,
+ PRIMARY KEY ("catalog_name", "namespace", "property_key"))`)
+ s.Require().NoError(err)
+
+ const (
+ catName = "default"
+ nsName = "test_v0_ns"
+ tblName = "test_v0_tbl"
+ metaLoc = "file:///does/not/matter/00000-v0.metadata.json"
+ )
+ _, err = sqldb.Exec(
+ `INSERT INTO "iceberg_namespace_properties" `+
+ `("catalog_name", "namespace", "property_key",
"property_value") VALUES (?, ?, ?, ?)`,
+ catName, nsName, "exists", "true")
+ s.Require().NoError(err)
+ _, err = sqldb.Exec(
+ `INSERT INTO "iceberg_tables" `+
+ `("catalog_name", "table_namespace", "table_name",
"metadata_location") VALUES (?, ?, ?, ?)`,
+ catName, nsName, tblName, metaLoc)
+ s.Require().NoError(err)
+
+ _ = s.loadCatalogForTableCreation()
+
+ var present int
+ err = sqldb.QueryRow(
+ `SELECT 1 FROM pragma_table_info('iceberg_tables') WHERE name =
'iceberg_type'`,
+ ).Scan(&present)
+ s.Require().NoError(err, "migration must add iceberg_type column")
+ s.Equal(1, present)
+
+ var got sql.NullString
+ err = sqldb.QueryRow(
+ `SELECT iceberg_type FROM iceberg_tables WHERE catalog_name = ?
AND table_namespace = ? AND table_name = ?`,
+ catName, nsName, tblName,
+ ).Scan(&got)
+ s.Require().NoError(err)
+ s.False(got.Valid, "pre-V0 row should keep iceberg_type IS NULL after
migration")
+
+ _ = s.loadCatalogForTableCreation()
+}
Review Comment:
This second `loadCatalogForTableCreation()` looks like it's meant to check
the migration is idempotent on re-open, but with no assertion or comment after
it, it isn't really pinning anything down. A line of comment plus a re-check
(column still present, row still NULL) would make the intent stick.
##########
catalog/sql/sql.go:
##########
@@ -247,7 +247,76 @@ func (c *Catalog) DropSQLTables(ctx context.Context) error
{
}
func (c *Catalog) ensureTablesExist() error {
- return c.CreateSQLTables(context.Background())
+ ctx := context.Background()
+ if err := c.CreateSQLTables(ctx); err != nil {
+ return err
+ }
+
+ return c.migrateV0Schema(ctx)
+}
+
+func (c *Catalog) migrateV0Schema(ctx context.Context) error {
+ hasCol, probeErr := c.icebergTypeColumnExists(ctx)
+ if probeErr != nil {
+ log.Printf("WARNING: skipping V0 schema migration; iceberg_type
column probe failed: %v", probeErr)
+
+ return nil
+ }
+ if hasCol {
+ return nil
+ }
+
+ if _, err := c.db.ExecContext(ctx, "ALTER TABLE iceberg_tables ADD
COLUMN iceberg_type VARCHAR(5)"); err != nil {
+ has, reprobeErr := c.icebergTypeColumnExists(ctx)
+ if has {
+ return nil
+ }
+ if reprobeErr != nil {
+ log.Printf("WARNING: V0 schema migration re-probe
failed after ALTER error: %v", reprobeErr)
+ }
+
+ return fmt.Errorf("migration of V0 schema failed: %w", err)
+ }
+
+ return nil
+}
+
+func (c *Catalog) icebergTypeColumnExists(ctx context.Context) (bool, error) {
+ var query string
+ switch dialectName := c.db.Dialect().Name().String(); dialectName {
+ case "pg":
+ query = `SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'iceberg_tables'
+ AND column_name = 'iceberg_type' LIMIT 1`
+ case "mysql":
+ query = `SELECT 1 FROM information_schema.columns
+ WHERE table_schema = DATABASE()
+ AND table_name = 'iceberg_tables'
+ AND column_name = 'iceberg_type' LIMIT 1`
+ case "mssql":
+ query = `SELECT TOP 1 1 FROM information_schema.columns
+ WHERE table_name = 'iceberg_tables'
+ AND column_name = 'iceberg_type'`
+ case "sqlite":
+ query = `SELECT 1 FROM pragma_table_info('iceberg_tables')
+ WHERE name = 'iceberg_type' LIMIT 1`
+ case "oracle":
+ query = `SELECT 1 FROM user_tab_columns
Review Comment:
Nit: this query's indentation mixes tabs and spaces where the other cases
use tabs. It's inside a raw string so gofmt won't catch it and the SQL runs
fine, but it'll look off in future diffs — worth realigning to match the others.
##########
catalog/sql/sql_test.go:
##########
@@ -322,6 +322,182 @@ func (s *SqliteCatalogTestSuite)
TestCreationAllTablesExist() {
s.confirmTablesExist(sqldb)
}
+func (s *SqliteCatalogTestSuite) TestV0SchemaFromIcebergIsMigrated() {
+ sqldb := s.getDB()
+ s.confirmNoTables(sqldb)
+
+ _, err := sqldb.Exec(`CREATE TABLE "iceberg_tables" (
+ "catalog_name" VARCHAR NOT NULL,
+ "table_namespace" VARCHAR NOT NULL,
+ "table_name" VARCHAR NOT NULL,
+ "metadata_location" VARCHAR,
+ "previous_metadata_location" VARCHAR,
+ PRIMARY KEY ("catalog_name", "table_namespace", "table_name"))`)
+ s.Require().NoError(err)
+ _, err = sqldb.Exec(`CREATE TABLE "iceberg_namespace_properties" (
+ "catalog_name" VARCHAR NOT NULL,
+ "namespace" VARCHAR NOT NULL,
+ "property_key" VARCHAR NOT NULL,
+ "property_value" VARCHAR,
+ PRIMARY KEY ("catalog_name", "namespace", "property_key"))`)
+ s.Require().NoError(err)
+
+ const (
+ catName = "default"
+ nsName = "test_v0_ns"
+ tblName = "test_v0_tbl"
+ metaLoc = "file:///does/not/matter/00000-v0.metadata.json"
+ )
+ _, err = sqldb.Exec(
+ `INSERT INTO "iceberg_namespace_properties" `+
+ `("catalog_name", "namespace", "property_key",
"property_value") VALUES (?, ?, ?, ?)`,
+ catName, nsName, "exists", "true")
+ s.Require().NoError(err)
+ _, err = sqldb.Exec(
+ `INSERT INTO "iceberg_tables" `+
+ `("catalog_name", "table_namespace", "table_name",
"metadata_location") VALUES (?, ?, ?, ?)`,
+ catName, nsName, tblName, metaLoc)
+ s.Require().NoError(err)
+
+ _ = s.loadCatalogForTableCreation()
+
+ var present int
+ err = sqldb.QueryRow(
+ `SELECT 1 FROM pragma_table_info('iceberg_tables') WHERE name =
'iceberg_type'`,
+ ).Scan(&present)
+ s.Require().NoError(err, "migration must add iceberg_type column")
+ s.Equal(1, present)
+
+ var got sql.NullString
+ err = sqldb.QueryRow(
+ `SELECT iceberg_type FROM iceberg_tables WHERE catalog_name = ?
AND table_namespace = ? AND table_name = ?`,
+ catName, nsName, tblName,
+ ).Scan(&got)
+ s.Require().NoError(err)
+ s.False(got.Valid, "pre-V0 row should keep iceberg_type IS NULL after
migration")
+
+ _ = s.loadCatalogForTableCreation()
+}
+
+func (s *SqliteCatalogTestSuite)
TestV0SchemaListAndDropAcceptNullIcebergType() {
+ sqldb := s.getDB()
+ s.confirmNoTables(sqldb)
+
+ _, err := sqldb.Exec(`CREATE TABLE "iceberg_tables" (
+ "catalog_name" VARCHAR NOT NULL,
+ "table_namespace" VARCHAR NOT NULL,
+ "table_name" VARCHAR NOT NULL,
+ "metadata_location" VARCHAR,
+ "previous_metadata_location" VARCHAR,
+ PRIMARY KEY ("catalog_name", "table_namespace", "table_name"))`)
+ s.Require().NoError(err)
+ _, err = sqldb.Exec(`CREATE TABLE "iceberg_namespace_properties" (
+ "catalog_name" VARCHAR NOT NULL,
+ "namespace" VARCHAR NOT NULL,
+ "property_key" VARCHAR NOT NULL,
+ "property_value" VARCHAR,
+ PRIMARY KEY ("catalog_name", "namespace", "property_key"))`)
+ s.Require().NoError(err)
+
+ const (
+ catName = "default"
+ nsName = "legacy_ns"
+ tblName = "legacy_tbl"
+ metaLoc = "file:///legacy/00000-v0.metadata.json"
+ )
+ _, err = sqldb.Exec(
+ `INSERT INTO "iceberg_namespace_properties" `+
+ `("catalog_name", "namespace", "property_key",
"property_value") VALUES (?, ?, ?, ?)`,
+ catName, nsName, "exists", "true")
+ s.Require().NoError(err)
+ _, err = sqldb.Exec(
+ `INSERT INTO "iceberg_tables" `+
+ `("catalog_name", "table_namespace", "table_name",
"metadata_location") VALUES (?, ?, ?, ?)`,
+ catName, nsName, tblName, metaLoc)
+ s.Require().NoError(err)
+
+ cat := s.loadCatalogForTableCreation()
+ ctx := context.Background()
+
+ var listed []table.Identifier
+ for ident, err := range cat.ListTables(ctx, table.Identifier{nsName}) {
+ s.Require().NoError(err)
+ listed = append(listed, ident)
+ }
+ s.Require().Len(listed, 1, "ListTables must return rows with
iceberg_type IS NULL")
+ s.Equal(tblName, listed[0][len(listed[0])-1])
+
+ s.Require().NoError(cat.DropTable(ctx, table.Identifier{nsName,
tblName}))
+
+ var remaining int
+ err = sqldb.QueryRow(
+ `SELECT COUNT(*) FROM iceberg_tables WHERE catalog_name = ? AND
table_namespace = ? AND table_name = ?`,
+ catName, nsName, tblName,
+ ).Scan(&remaining)
+ s.Require().NoError(err)
+ s.Equal(0, remaining, "DropTable must delete rows with iceberg_type IS
NULL")
+}
+
+func (s *SqliteCatalogTestSuite)
TestV0SchemaLoadCommitRenameAcceptNullIcebergType() {
+ sqldb := s.getDB()
Review Comment:
This exercises a row created at V1 and then manually NULLed — a fine proxy,
but not the same as a genuine V0 row where the column was absent until the
migration ran; a one-line comment noting the setup depends on migration having
already happened would help. Couple of smaller things in here too: the closure
hardcodes `"default"` as the catalog name (`cat.Name()` would be less fragile),
and `EqualValues(1, n)` could be `Equal(int64(1), n)` for a stricter check.
##########
catalog/sql/sql_test.go:
##########
@@ -322,6 +322,182 @@ func (s *SqliteCatalogTestSuite)
TestCreationAllTablesExist() {
s.confirmTablesExist(sqldb)
}
+func (s *SqliteCatalogTestSuite) TestV0SchemaFromIcebergIsMigrated() {
+ sqldb := s.getDB()
Review Comment:
The coverage here is all SQLite, which leaves the riskiest paths untested:
the `mssql`/`oracle` probe + ALTER (where the `ADD COLUMN` syntax bug lives),
the probe-error → skip-migration branch, and the concurrent-ALTER re-probe
recovery. The first in particular means CI won't catch a V0 catalog being
unopenable on those engines. Even a focused test that injects a failing probe
would cover the swallow path.
##########
catalog/sql/sql.go:
##########
@@ -247,7 +247,76 @@ func (c *Catalog) DropSQLTables(ctx context.Context) error
{
}
func (c *Catalog) ensureTablesExist() error {
- return c.CreateSQLTables(context.Background())
+ ctx := context.Background()
+ if err := c.CreateSQLTables(ctx); err != nil {
+ return err
+ }
+
+ return c.migrateV0Schema(ctx)
+}
+
+func (c *Catalog) migrateV0Schema(ctx context.Context) error {
+ hasCol, probeErr := c.icebergTypeColumnExists(ctx)
+ if probeErr != nil {
+ log.Printf("WARNING: skipping V0 schema migration; iceberg_type
column probe failed: %v", probeErr)
+
+ return nil
+ }
+ if hasCol {
+ return nil
+ }
+
+ if _, err := c.db.ExecContext(ctx, "ALTER TABLE iceberg_tables ADD
COLUMN iceberg_type VARCHAR(5)"); err != nil {
Review Comment:
This ALTER is hardcoded, but `ADD COLUMN` isn't valid on every dialect the
probe handles. SQL Server wants `ALTER TABLE iceberg_tables ADD iceberg_type
VARCHAR(5)` (no `COLUMN`) and Oracle wants `ADD (iceberg_type VARCHAR2(5))`.
Since the probe already has `mssql` and `oracle` cases, a V0 catalog on either
engine reaches this line, the ALTER fails, the re-probe still finds the column
missing, and `migrateV0Schema` returns a hard error — so the catalog can't be
opened at all. I'd switch on the dialect here the same way the probe does and
emit dialect-specific DDL.
Separately, `VARCHAR(5)` diverges from the unbounded `VARCHAR` bun emits for
fresh Go-created tables, so migrated and fresh DBs end up with different column
types — and it fits `"TABLE"` exactly, which is tight. I'd drop the length
bound to match what bun generates.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]