Skip to content

Commit 50e10ad

Browse files
Fix misleading iceberg catalog option error messages
The FDW validator in pg_lake_iceberg used error messages that said options were "only valid for writable catalog=\"rest\"" when they are actually valid for read-only external catalog tables (both catalog=rest and catalog=object_store). Similarly, "required for catalog=\"rest\"" omitted that the same options are required for catalog=object_store read-only tables. The error raised for an unknown "catalog" value also listed only "rest" and "postgres", omitting "object_store". Changes: - Reword all "only valid for" / "required for" error messages for catalog_name, catalog_namespace, catalog_table_name, and read_only to accurately describe which catalog types they apply to. - Update the "invalid catalog option" errdetail and surrounding block comment to include object_store alongside rest and postgres. - Remove a dead "catalog_namespace required" check: it only applied to REST read-only (object_store excluded), but the REST ALTER whitelist never allows touching catalog_namespace and CREATE auto-fills a default, so the path was unreachable. - Rework the comments in ProcessAlterTable: the outer block comment now describes external catalog handling (was "rest" only) and the validator interaction, and each per-branch comment accurately describes which options that catalog type allows changing (the previous object_store branch comment was a stale copy-paste of the rest branch comment). - Add a comment in option.c noting that the remaining "required" checks act as a safety net for ALTER DROP scenarios. Tests: - test_iceberg_catalog_option_validation_errors (test_writable_iceberg.py): exercises the four "only valid for read-only external catalog tables" rejection paths plus the "invalid catalog option" path without needing external infrastructure. - test_object_store_read_only_alter_drop_required_options (test_object_store_catalog.py): exercises the two "required" paths via ALTER FOREIGN TABLE OPTIONS (DROP ...) on an object_store read-only table. Co-authored-by: Cursor <cursoragent@cursor.com> Signed-off-by: sfc-gh-npuka <naisila.puka@snowflake.com>
1 parent 9e7f568 commit 50e10ad

4 files changed

Lines changed: 148 additions & 23 deletions

File tree

pg_lake_table/src/ddl/alter_table.c

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -248,9 +248,11 @@ ProcessAlterTable(ProcessUtilityParams * processUtilityParams, void *arg)
248248
}
249249

250250
/*
251-
* For read-only rest catalog iceberg tables, we only allow changing
252-
* catalog_table_name option. All other operations or any other DDLs are
253-
* disallowed.
251+
* For read-only external catalog iceberg tables, we only allow changing a
252+
* subset of catalog options. All other operations are disallowed. After
253+
* the whitelist check passes, we return false so PostgreSQL processes the
254+
* ALTER normally (which invokes the FDW validator on the merged option
255+
* set).
254256
*/
255257
if (HasOnlyCatalogAlterTableOptions(alterStmt))
256258
{
@@ -259,8 +261,9 @@ ProcessAlterTable(ProcessUtilityParams * processUtilityParams, void *arg)
259261
if (icebergCatalogType == REST_CATALOG_READ_ONLY)
260262
{
261263
/*
262-
* We currently only allow changing catalog_table_name option for
263-
* read-only rest catalog iceberg tables.
264+
* For read-only rest catalog iceberg tables, only
265+
* catalog_table_name can be changed. catalog_name and
266+
* catalog_namespace are pinned at creation time.
264267
*/
265268
List *allowedOptions = list_make1("catalog_table_name");
266269

@@ -271,8 +274,10 @@ ProcessAlterTable(ProcessUtilityParams * processUtilityParams, void *arg)
271274
else if (icebergCatalogType == OBJECT_STORE_READ_ONLY)
272275
{
273276
/*
274-
* We currently only allow changing catalog_table_name option for
275-
* read-only rest catalog iceberg tables.
277+
* For read-only object_store catalog iceberg tables, the full
278+
* catalog identifier (catalog_name + catalog_namespace +
279+
* catalog_table_name) can be changed to re-point the table at a
280+
* different source.
276281
*/
277282
List *allowedOptions = list_make3("catalog_table_name", "catalog_namespace", "catalog_name");
278283

pg_lake_table/src/fdw/option.c

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -765,8 +765,9 @@ pg_lake_iceberg_validator(PG_FUNCTION_ARGS)
765765
char *icebergCatalogName = defGetString(def);
766766

767767
/*
768-
* We only accept "rest" and "postgres" for now. If not provided,
769-
* assume "postgres" by default. Don't allow anything.
768+
* We only accept "rest", "object_store" and "postgres". If not
769+
* provided, the postgres catalog is assumed by default (see
770+
* ProcessCreateIcebergTableFromForeignTableStmt).
770771
*/
771772
if (pg_strncasecmp(icebergCatalogName, REST_CATALOG_NAME, strlen(icebergCatalogName)) == 0)
772773
{
@@ -793,7 +794,7 @@ pg_lake_iceberg_validator(PG_FUNCTION_ARGS)
793794
ereport(ERROR,
794795
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
795796
errmsg("invalid catalog option: %s", icebergCatalogName),
796-
errdetail("Only " REST_CATALOG_NAME " and " POSTGRES_CATALOG_NAME " are supported for now.")));
797+
errdetail("Only " REST_CATALOG_NAME ", " OBJECT_STORE_CATALOG_NAME " and " POSTGRES_CATALOG_NAME " are supported.")));
797798
}
798799
else if (catalog == ForeignTableRelationId && strcmp(def->defname, "read_only") == 0)
799800
{
@@ -908,30 +909,30 @@ pg_lake_iceberg_validator(PG_FUNCTION_ARGS)
908909
!(icebergCatalogType == REST_CATALOG_READ_ONLY || icebergCatalogType == OBJECT_STORE_READ_ONLY))
909910
ereport(ERROR,
910911
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
911-
errmsg("\"read_only\" option is only valid for catalog=\"rest\"")));
912+
errmsg("\"read_only\" option is only valid for catalog=\"rest\" or catalog=\"object_store\"")));
912913

913914
if (catalog == ForeignTableRelationId)
914915
{
915916
if (icebergCatalogType == REST_CATALOG_READ_ONLY || icebergCatalogType == OBJECT_STORE_READ_ONLY)
916917
{
917918
/*
918-
* catalog_namespace, catalog_table_name and catalog_name is
919-
* required for catalog=rest
919+
* catalog_name and catalog_table_name are required for read-only
920+
* external catalog tables, i.e. catalog=rest and
921+
* catalog=object_store
922+
*
923+
* On CREATE, ProcessCreateIcebergTableFromForeignTableStmt
924+
* auto-fills defaults for these options, so these checks act as a
925+
* safety net for ALTER DROP scenarios.
920926
*/
921927
if (!catalogName)
922928
ereport(ERROR,
923929
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
924-
errmsg("\"catalog_name\" option is required for catalog=\"rest\"")));
925-
926-
if (!catalogNamespace && icebergCatalogType != OBJECT_STORE_READ_ONLY)
927-
ereport(ERROR,
928-
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
929-
errmsg("\"catalog_namespace\" option is required for catalog=\"rest\"")));
930+
errmsg("\"catalog_name\" option is required for read-only external catalog tables")));
930931

931932
if (!catalogTableName)
932933
ereport(ERROR,
933934
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
934-
errmsg("\"catalog_table_name\" option is required for catalog=\"rest\"")));
935+
errmsg("\"catalog_table_name\" option is required for read-only external catalog tables")));
935936
}
936937
else
937938
{
@@ -941,17 +942,17 @@ pg_lake_iceberg_validator(PG_FUNCTION_ARGS)
941942
if (catalogNamespace)
942943
ereport(ERROR,
943944
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
944-
errmsg("\"catalog_namespace\" option is only valid for writable catalog=\"rest\"")));
945+
errmsg("\"catalog_namespace\" option is only valid for read-only external catalog tables")));
945946

946947
if (catalogTableName)
947948
ereport(ERROR,
948949
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
949-
errmsg("\"catalog_table_name\" option is only valid for writable catalog=\"rest\"")));
950+
errmsg("\"catalog_table_name\" option is only valid for read-only external catalog tables")));
950951

951952
if (catalogName)
952953
ereport(ERROR,
953954
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
954-
errmsg("\"catalog_name\" option is only valid for writable catalog=\"rest\"")));
955+
errmsg("\"catalog_name\" option is only valid for read-only external catalog tables")));
955956

956957
}
957958
}

pg_lake_table/tests/pytests/test_object_store_catalog.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,53 @@ def test_read_only_object_store_with_non_existing_options(
127127
pg_conn.commit()
128128

129129

130+
def test_object_store_read_only_alter_drop_required_options(
131+
pg_conn, s3, extension, with_default_location, adjust_object_store_settings
132+
):
133+
schema = "test_alter_drop_required"
134+
run_command(f"CREATE SCHEMA {schema}", pg_conn)
135+
136+
run_command(
137+
f"CREATE TABLE {schema}.wrt_tbl(a INT) USING iceberg WITH (catalog='object_store')",
138+
pg_conn,
139+
)
140+
pg_conn.commit()
141+
wait_until_object_store_writable_table_pushed(pg_conn, schema, "wrt_tbl")
142+
143+
run_command(
144+
f"CREATE TABLE {schema}.ro_tbl() USING iceberg WITH (catalog='object_store', read_only=True, catalog_table_name='wrt_tbl')",
145+
pg_conn,
146+
)
147+
pg_conn.commit()
148+
149+
# dropping catalog_name should fail — it is required
150+
error = run_command(
151+
f"ALTER FOREIGN TABLE {schema}.ro_tbl OPTIONS (DROP catalog_name)",
152+
pg_conn,
153+
raise_error=False,
154+
)
155+
assert (
156+
'"catalog_name" option is required for read-only external catalog tables'
157+
in str(error)
158+
)
159+
pg_conn.rollback()
160+
161+
# dropping catalog_table_name should fail — it is required
162+
error = run_command(
163+
f"ALTER FOREIGN TABLE {schema}.ro_tbl OPTIONS (DROP catalog_table_name)",
164+
pg_conn,
165+
raise_error=False,
166+
)
167+
assert (
168+
'"catalog_table_name" option is required for read-only external catalog tables'
169+
in str(error)
170+
)
171+
pg_conn.rollback()
172+
173+
run_command(f"DROP SCHEMA {schema} CASCADE", pg_conn)
174+
pg_conn.commit()
175+
176+
130177
# basic flow for object store catalog tables
131178
# changes on the source is reflected on the target
132179
def test_read_only_object_store_read_write(

pg_lake_table/tests/pytests/test_writable_iceberg.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,78 @@ def test_writable_iceberg_create_wrong_option(pg_conn, extension):
120120
pg_conn.commit()
121121

122122

123+
def test_iceberg_catalog_option_validation_errors(pg_conn, extension):
124+
schema = "test_catalog_option_validation"
125+
run_command(f"CREATE SCHEMA {schema}", pg_conn)
126+
pg_conn.commit()
127+
128+
# only rest, object_store and postgres are accepted as catalog values
129+
error = run_command(
130+
f"""CREATE FOREIGN TABLE {schema}.ft (id int) SERVER pg_lake_iceberg
131+
OPTIONS (location 's3://bucket/path', catalog 'bogus')""",
132+
pg_conn,
133+
raise_error=False,
134+
)
135+
assert "invalid catalog option: bogus" in str(error)
136+
assert "Only rest, object_store and postgres are supported" in str(error)
137+
pg_conn.rollback()
138+
139+
# read_only is only valid for catalog="rest" or catalog="object_store"
140+
error = run_command(
141+
f"""CREATE FOREIGN TABLE {schema}.ft (id int) SERVER pg_lake_iceberg
142+
OPTIONS (location 's3://bucket/path', read_only 'true')""",
143+
pg_conn,
144+
raise_error=False,
145+
)
146+
assert (
147+
'"read_only" option is only valid for catalog="rest" or catalog="object_store"'
148+
in str(error)
149+
)
150+
pg_conn.rollback()
151+
152+
# catalog_name is only valid for read-only external catalog tables
153+
error = run_command(
154+
f"""CREATE FOREIGN TABLE {schema}.ft (id int) SERVER pg_lake_iceberg
155+
OPTIONS (location 's3://bucket/path', catalog_name 'cat')""",
156+
pg_conn,
157+
raise_error=False,
158+
)
159+
assert (
160+
'"catalog_name" option is only valid for read-only external catalog tables'
161+
in str(error)
162+
)
163+
pg_conn.rollback()
164+
165+
# catalog_namespace is only valid for read-only external catalog tables
166+
error = run_command(
167+
f"""CREATE FOREIGN TABLE {schema}.ft (id int) SERVER pg_lake_iceberg
168+
OPTIONS (location 's3://bucket/path', catalog_namespace 'ns')""",
169+
pg_conn,
170+
raise_error=False,
171+
)
172+
assert (
173+
'"catalog_namespace" option is only valid for read-only external catalog tables'
174+
in str(error)
175+
)
176+
pg_conn.rollback()
177+
178+
# catalog_table_name is only valid for read-only external catalog tables
179+
error = run_command(
180+
f"""CREATE FOREIGN TABLE {schema}.ft (id int) SERVER pg_lake_iceberg
181+
OPTIONS (location 's3://bucket/path', catalog_table_name 'tbl')""",
182+
pg_conn,
183+
raise_error=False,
184+
)
185+
assert (
186+
'"catalog_table_name" option is only valid for read-only external catalog tables'
187+
in str(error)
188+
)
189+
pg_conn.rollback()
190+
191+
run_command(f"DROP SCHEMA {schema} CASCADE", pg_conn)
192+
pg_conn.commit()
193+
194+
123195
def test_writable_iceberg_table_failure(
124196
installcheck,
125197
install_iceberg_to_duckdb,

0 commit comments

Comments
 (0)