From fb4d04b7c5486fa18dac9b27782ea3ba6a103fcf Mon Sep 17 00:00:00 2001 From: Jelte Fennema-Nio Date: Sat, 28 Jun 2025 23:20:19 +0200 Subject: [PATCH 1/3] Undo required changes for a PG18 commit that got reverted --- .github/workflows/build_and_test.yaml | 2 +- src/pgduckdb_hooks.cpp | 17 ++--------------- src/scan/postgres_table_reader.cpp | 5 ----- 3 files changed, 3 insertions(+), 21 deletions(-) diff --git a/.github/workflows/build_and_test.yaml b/.github/workflows/build_and_test.yaml index 221833f6..c12eb86f 100644 --- a/.github/workflows/build_and_test.yaml +++ b/.github/workflows/build_and_test.yaml @@ -52,7 +52,7 @@ jobs: type: ReleaseStatic - version: REL_17_STABLE type: Debug - - version: REL_18_BETA1 + - version: master type: Release # Not enabled for now (waiting for April 2025 code freeze) diff --git a/src/pgduckdb_hooks.cpp b/src/pgduckdb_hooks.cpp index c3cb049c..1fb2e7c6 100644 --- a/src/pgduckdb_hooks.cpp +++ b/src/pgduckdb_hooks.cpp @@ -331,31 +331,18 @@ DuckdbExecutorStartHook_Cpp(QueryDesc *queryDesc) { pgduckdb::ClaimCurrentCommandId(); } -#if PG_VERSION_NUM >= 180000 -static bool -#else static void -#endif DuckdbExecutorStartHook(QueryDesc *queryDesc, int eflags) { pgduckdb::executor_nest_level++; if (!pgduckdb::IsExtensionRegistered()) { pgduckdb::MarkStatementNotTopLevel(); - return prev_executor_start_hook(queryDesc, eflags); + prev_executor_start_hook(queryDesc, eflags); + return; } -#if PG_VERSION_NUM >= 180000 - if (!prev_executor_start_hook(queryDesc, eflags)) { - return false; - } -#else prev_executor_start_hook(queryDesc, eflags); -#endif InvokeCPPFunc(DuckdbExecutorStartHook_Cpp, queryDesc); - -#if PG_VERSION_NUM >= 180000 - return true; -#endif } /* diff --git a/src/scan/postgres_table_reader.cpp b/src/scan/postgres_table_reader.cpp index 5d43383f..314145b5 100644 --- a/src/scan/postgres_table_reader.cpp +++ b/src/scan/postgres_table_reader.cpp @@ -65,13 +65,8 @@ PostgresTableReader::InitUnsafe(const char *table_scan_query, bool count_tuples_ PlannedStmt *planned_stmt = standard_planner(query, table_scan_query, 0, nullptr); -#if PG_VERSION_NUM >= 180000 - table_scan_query_desc = CreateQueryDesc(planned_stmt, nullptr, table_scan_query, GetActiveSnapshot(), - InvalidSnapshot, None_Receiver, nullptr, nullptr, 0); -#else table_scan_query_desc = CreateQueryDesc(planned_stmt, table_scan_query, GetActiveSnapshot(), InvalidSnapshot, None_Receiver, nullptr, nullptr, 0); -#endif ExecutorStart(table_scan_query_desc, 0); From 9ecb7e460f8fc929cb6490faa14cf2091fab6ae5 Mon Sep 17 00:00:00 2001 From: Jelte Fennema-Nio Date: Sun, 29 Jun 2025 01:42:28 +0200 Subject: [PATCH 2/3] Always have duckdb create column aliases for top level subscripts --- include/pgduckdb/pgduckdb_ruleutils.h | 2 +- src/pgduckdb_ruleutils.cpp | 5 +---- src/vendor/pg_ruleutils_14.c | 2 +- src/vendor/pg_ruleutils_15.c | 2 +- src/vendor/pg_ruleutils_16.c | 2 +- src/vendor/pg_ruleutils_17.c | 2 +- src/vendor/pg_ruleutils_18.c | 2 +- test/regression/expected/read_functions.out | 10 +++++----- 8 files changed, 12 insertions(+), 15 deletions(-) diff --git a/include/pgduckdb/pgduckdb_ruleutils.h b/include/pgduckdb/pgduckdb_ruleutils.h index dcadaf53..9e578608 100644 --- a/include/pgduckdb/pgduckdb_ruleutils.h +++ b/include/pgduckdb/pgduckdb_ruleutils.h @@ -22,7 +22,7 @@ bool pgduckdb_is_unresolved_type(Oid type_oid); bool pgduckdb_is_fake_type(Oid type_oid); bool pgduckdb_var_is_duckdb_row(Var *var); bool pgduckdb_func_returns_duckdb_row(RangeTblFunction *rtfunc); -Var *pgduckdb_duckdb_row_subscript_var(Expr *expr); +Var *pgduckdb_duckdb_subscript_var(Expr *expr); bool pgduckdb_reconstruct_star_step(StarReconstructionContext *ctx, ListCell *tle_cell); bool pgduckdb_replace_subquery_with_view(Query *query, StringInfo buf); int pgduckdb_show_type(Const *constval, int original_showtype); diff --git a/src/pgduckdb_ruleutils.cpp b/src/pgduckdb_ruleutils.cpp index e125913b..848cd468 100644 --- a/src/pgduckdb_ruleutils.cpp +++ b/src/pgduckdb_ruleutils.cpp @@ -126,7 +126,7 @@ pgduckdb_func_returns_duckdb_row(RangeTblFunction *rtfunc) { * the Var of the duckdb row if it is. */ Var * -pgduckdb_duckdb_row_subscript_var(Expr *expr) { +pgduckdb_duckdb_subscript_var(Expr *expr) { if (!expr) { return NULL; } @@ -143,9 +143,6 @@ pgduckdb_duckdb_row_subscript_var(Expr *expr) { Var *refexpr = (Var *)subscript->refexpr; - if (!pgduckdb_var_is_duckdb_row(refexpr)) { - return NULL; - } return refexpr; } diff --git a/src/vendor/pg_ruleutils_14.c b/src/vendor/pg_ruleutils_14.c index c870babb..bb063dcd 100644 --- a/src/vendor/pg_ruleutils_14.c +++ b/src/vendor/pg_ruleutils_14.c @@ -6075,7 +6075,7 @@ get_target_list(List *targetList, deparse_context *context, * to the column name are still valid. */ if (!duckdb_skip_as && outermost_targetlist) { - Var *subscript_var = pgduckdb_duckdb_row_subscript_var(tle->expr); + Var *subscript_var = pgduckdb_duckdb_subscript_var(tle->expr); if (subscript_var) { /* * This cannot be moved to pgduckdb_ruleutils, because of diff --git a/src/vendor/pg_ruleutils_15.c b/src/vendor/pg_ruleutils_15.c index 968470e0..18b54194 100644 --- a/src/vendor/pg_ruleutils_15.c +++ b/src/vendor/pg_ruleutils_15.c @@ -6156,7 +6156,7 @@ get_target_list(List *targetList, deparse_context *context, * to the column name are still valid. */ if (!duckdb_skip_as && outermost_targetlist) { - Var *subscript_var = pgduckdb_duckdb_row_subscript_var(tle->expr); + Var *subscript_var = pgduckdb_duckdb_subscript_var(tle->expr); if (subscript_var) { /* * This cannot be moved to pgduckdb_ruleutils, because of diff --git a/src/vendor/pg_ruleutils_16.c b/src/vendor/pg_ruleutils_16.c index feaa8bf0..200df3de 100644 --- a/src/vendor/pg_ruleutils_16.c +++ b/src/vendor/pg_ruleutils_16.c @@ -6115,7 +6115,7 @@ get_target_list(List *targetList, deparse_context *context, * to the column name are still valid. */ if (!duckdb_skip_as && outermost_targetlist) { - Var *subscript_var = pgduckdb_duckdb_row_subscript_var(tle->expr); + Var *subscript_var = pgduckdb_duckdb_subscript_var(tle->expr); if (subscript_var) { /* * This cannot be moved to pgduckdb_ruleutils, because of diff --git a/src/vendor/pg_ruleutils_17.c b/src/vendor/pg_ruleutils_17.c index 87667319..e666d2d7 100644 --- a/src/vendor/pg_ruleutils_17.c +++ b/src/vendor/pg_ruleutils_17.c @@ -6129,7 +6129,7 @@ get_target_list(List *targetList, deparse_context *context, * to the column name are still valid. */ if (!duckdb_skip_as && outermost_targetlist) { - Var *subscript_var = pgduckdb_duckdb_row_subscript_var(tle->expr); + Var *subscript_var = pgduckdb_duckdb_subscript_var(tle->expr); if (subscript_var) { /* * This cannot be moved to pgduckdb_ruleutils, because of diff --git a/src/vendor/pg_ruleutils_18.c b/src/vendor/pg_ruleutils_18.c index ae6dcd8c..abb9bf2d 100644 --- a/src/vendor/pg_ruleutils_18.c +++ b/src/vendor/pg_ruleutils_18.c @@ -6333,7 +6333,7 @@ get_target_list(List *targetList, deparse_context *context) * to the column name are still valid. */ if (!duckdb_skip_as && outermost_targetlist) { - Var *subscript_var = pgduckdb_duckdb_row_subscript_var(tle->expr); + Var *subscript_var = pgduckdb_duckdb_subscript_var(tle->expr); if (subscript_var) { /* * This cannot be moved to pgduckdb_ruleutils, because of diff --git a/test/regression/expected/read_functions.out b/test/regression/expected/read_functions.out index e91e71a9..2d104342 100644 --- a/test/regression/expected/read_functions.out +++ b/test/regression/expected/read_functions.out @@ -41,9 +41,9 @@ SELECT jsoncol[1], arraycol[2] FROM ( SELECT r['jsoncol'] jsoncol, r['arraycol'] arraycol FROM read_parquet('../../data/indexable.parquet') r ) q; - jsoncol | arraycol ----------+---------- - "d" | 22 + jsoncol[1] | arraycol[2] +------------+------------- + "d" | 22 (1 row) -- And the same for slice subscripts @@ -57,8 +57,8 @@ SELECT arraycol[1:2] FROM ( SELECT r['arraycol'] arraycol FROM read_parquet('../../data/indexable.parquet') r ) q; - arraycol ----------- + arraycol[1:2] +--------------- {11,22} (1 row) From 062dc59a5935d043b1299272b4d999765a68a64c Mon Sep 17 00:00:00 2001 From: Jelte Fennema-Nio Date: Sun, 29 Jun 2025 00:55:31 +0200 Subject: [PATCH 3/3] Try out the dot-subscripting patch There is a patch on the Postgres mailinglist that allows https://commitfest.postgresql.org/patch/5214/ --- .github/workflows/build_and_test.yaml | 10 +- src/pg/pgduckdb_subscript.cpp | 92 +++++++++++++++---- src/pgduckdb_ruleutils.cpp | 17 ++++ src/vendor/pg_ruleutils_18.c | 32 +++++-- test/pycheck/motherduck_test.py | 11 ++- .../expected/json_functions_duckdb.out | 9 ++ test/regression/expected/read_functions.out | 15 +-- test/regression/expected/unresolved_type.out | 6 ++ test/regression/sql/json_functions_duckdb.sql | 3 + test/regression/sql/read_functions.sql | 2 +- test/regression/sql/unresolved_type.sql | 2 + 11 files changed, 158 insertions(+), 41 deletions(-) diff --git a/.github/workflows/build_and_test.yaml b/.github/workflows/build_and_test.yaml index c12eb86f..06d17710 100644 --- a/.github/workflows/build_and_test.yaml +++ b/.github/workflows/build_and_test.yaml @@ -40,19 +40,25 @@ jobs: matrix: include: - version: REL_14_STABLE + repo: postgres/postgres type: Release - version: REL_15_STABLE + repo: postgres/postgres type: Release - version: REL_16_STABLE + repo: postgres/postgres type: Release # For PG17 we build DuckDB as a static library. There's nothing special # about the static library, nor PG17. This is only done so that we have # CI coverage for our logic to build a static library version. - version: REL_17_STABLE + repo: postgres/postgres type: ReleaseStatic - version: REL_17_STABLE + repo: postgres/postgres type: Debug - - version: master + - version: cf/5214 + repo: postgresql-cfbot/postgresql type: Release # Not enabled for now (waiting for April 2025 code freeze) @@ -89,7 +95,7 @@ jobs: - name: Checkout PostgreSQL code run: | rm -rf postgres - git clone --branch ${{ matrix.version }} --single-branch --depth 1 https://github.com/postgres/postgres.git + git clone --branch ${{ matrix.version }} --single-branch --depth 1 https://github.com/${{ matrix.repo }}.git postgres - name: Compute Version SHAs id: versions diff --git a/src/pg/pgduckdb_subscript.cpp b/src/pg/pgduckdb_subscript.cpp index c358d9a5..651a2614 100644 --- a/src/pg/pgduckdb_subscript.cpp +++ b/src/pg/pgduckdb_subscript.cpp @@ -89,6 +89,27 @@ AddSubscriptExpressions(SubscriptingRef *sbsref, struct ParseState *pstate, A_In } } +bool +AddSubscriptExpressions(SubscriptingRef *sbsref, struct ParseState *pstate, Node *subscript, bool is_slice) { + if (IsA(subscript, A_Indices)) { + // If the subscript is an A_Indices node, we can add the expressions directly + AddSubscriptExpressions(sbsref, pstate, castNode(A_Indices, subscript), is_slice); + return true; + } + + if (IsA(subscript, String)) { + sbsref->refupperindexpr = lappend(sbsref->refupperindexpr, subscript); + return true; + } + + if (IsA(subscript, A_Star)) { + sbsref->refupperindexpr = lappend(sbsref->refupperindexpr, NULL); + return true; + } + + return false; +} + /* * DuckdbSubscriptTransform is called by the parser when a subscripting * operation is performed on a duckdb type that can be indexed by arbitrary @@ -96,7 +117,7 @@ AddSubscriptExpressions(SubscriptingRef *sbsref, struct ParseState *pstate, A_In * subscript returns an an duckdb.unresolved_type again. */ void -DuckdbSubscriptTransform(SubscriptingRef *sbsref, List *indirection, struct ParseState *pstate, bool is_slice, +DuckdbSubscriptTransform(SubscriptingRef *sbsref, List **indirection, struct ParseState *pstate, bool is_slice, bool is_assignment, const char *type_name) { /* * We need to populate our cache for some of the code below. Normally this @@ -111,18 +132,22 @@ DuckdbSubscriptTransform(SubscriptingRef *sbsref, List *indirection, struct Pars elog(ERROR, "Assignment to %s is not supported", type_name); } - if (indirection == NIL) { + if (*indirection == NIL) { elog(ERROR, "Subscripting %s with an empty subscript is not supported", type_name); } // Transform each subscript expression - foreach_node(A_Indices, subscript, indirection) { - AddSubscriptExpressions(sbsref, pstate, subscript, is_slice); + foreach_ptr(Node, subscript, *indirection) { + if (!AddSubscriptExpressions(sbsref, pstate, subscript, is_slice)) { + break; + } } // Set the result type of the subscripting operation sbsref->refrestype = pgduckdb::DuckdbUnresolvedTypeOid(); sbsref->reftypmod = -1; + + *indirection = list_delete_first_n(*indirection, list_length(sbsref->refupperindexpr)); } /* @@ -136,7 +161,7 @@ DuckdbSubscriptTransform(SubscriptingRef *sbsref, List *indirection, struct Pars * Currently this is used for duckdb.row and duckdb.struct types. */ void -DuckdbTextSubscriptTransform(SubscriptingRef *sbsref, List *indirection, struct ParseState *pstate, bool is_slice, +DuckdbTextSubscriptTransform(SubscriptingRef *sbsref, List **indirection, struct ParseState *pstate, bool is_slice, bool is_assignment, const char *type_name) { /* * We need to populate our cache for some of the code below. Normally this @@ -151,20 +176,23 @@ DuckdbTextSubscriptTransform(SubscriptingRef *sbsref, List *indirection, struct elog(ERROR, "Assignment to %s is not supported", type_name); } - if (indirection == NIL) { + if (*indirection == NIL) { elog(ERROR, "Subscripting %s with an empty subscript is not supported", type_name); } bool first = true; // Transform each subscript expression - foreach_node(A_Indices, subscript, indirection) { - /* The first subscript needs to be a TEXT constant, since it should be - * a column reference. But the subscripts after that can be anything, - * DuckDB should interpret those. */ - if (first) { - sbsref->refupperindexpr = - lappend(sbsref->refupperindexpr, CoerceSubscriptToText(pstate, subscript, type_name)); + foreach_ptr(Node, subscript, *indirection) { + /* + * If the first subscript is an index expression then it needs to be + * coerced to text, since it should be a column reference. But the + * subscripts after that can be anything, DuckDB should interpret + * those. + */ + if (first && IsA(subscript, A_Indices)) { + sbsref->refupperindexpr = lappend(sbsref->refupperindexpr, + CoerceSubscriptToText(pstate, castNode(A_Indices, subscript), type_name)); if (is_slice) { sbsref->reflowerindexpr = lappend(sbsref->reflowerindexpr, NULL); } @@ -172,12 +200,16 @@ DuckdbTextSubscriptTransform(SubscriptingRef *sbsref, List *indirection, struct continue; } - AddSubscriptExpressions(sbsref, pstate, subscript, is_slice); + if (!AddSubscriptExpressions(sbsref, pstate, subscript, is_slice)) { + break; + } } // Set the result type of the subscripting operation sbsref->refrestype = pgduckdb::DuckdbUnresolvedTypeOid(); sbsref->reftypmod = -1; + + *indirection = list_delete_first_n(*indirection, list_length(sbsref->refupperindexpr)); } static bool @@ -229,8 +261,14 @@ DuckdbSubscriptExecSetup(const SubscriptingRef * /*sbsref*/, SubscriptingRefStat } void -DuckdbRowSubscriptTransform(SubscriptingRef *sbsref, List *indirection, struct ParseState *pstate, bool is_slice, +#if PG_VERSION_NUM >= 180000 +DuckdbRowSubscriptTransform(SubscriptingRef *sbsref, List **indirection, struct ParseState *pstate, bool is_slice, bool is_assignment) { +#else +DuckdbRowSubscriptTransform(SubscriptingRef *sbsref, List *indirection_, struct ParseState *pstate, bool is_slice, + bool is_assignment) { + List **indirection = &indirection_; +#endif DuckdbTextSubscriptTransform(sbsref, indirection, pstate, is_slice, is_assignment, "duckdb.row"); } @@ -249,8 +287,14 @@ static SubscriptRoutines duckdb_row_subscript_routines = { }; void -DuckdbUnresolvedTypeSubscriptTransform(SubscriptingRef *sbsref, List *indirection, struct ParseState *pstate, +#if PG_VERSION_NUM >= 180000 +DuckdbUnresolvedTypeSubscriptTransform(SubscriptingRef *sbsref, List **indirection, struct ParseState *pstate, + bool is_slice, bool is_assignment) { +#else +DuckdbUnresolvedTypeSubscriptTransform(SubscriptingRef *sbsref, List *indirection_, struct ParseState *pstate, bool is_slice, bool is_assignment) { + List **indirection = &indirection_; +#endif DuckdbSubscriptTransform(sbsref, indirection, pstate, is_slice, is_assignment, "duckdb.unresolved_type"); } @@ -269,8 +313,14 @@ static SubscriptRoutines duckdb_unresolved_type_subscript_routines = { }; void -DuckdbStructSubscriptTransform(SubscriptingRef *sbsref, List *indirection, struct ParseState *pstate, bool is_slice, +#if PG_VERSION_NUM >= 180000 +DuckdbStructSubscriptTransform(SubscriptingRef *sbsref, List **indirection, struct ParseState *pstate, bool is_slice, bool is_assignment) { +#else +DuckdbStructSubscriptTransform(SubscriptingRef *sbsref, List *indirection_, struct ParseState *pstate, bool is_slice, + bool is_assignment) { + List **indirection = &indirection_; +#endif DuckdbTextSubscriptTransform(sbsref, indirection, pstate, is_slice, is_assignment, "duckdb.struct"); } @@ -289,8 +339,14 @@ static SubscriptRoutines duckdb_struct_subscript_routines = { }; void -DuckdbMapSubscriptTransform(SubscriptingRef *sbsref, List *indirection, struct ParseState *pstate, bool is_slice, +#if PG_VERSION_NUM >= 180000 +DuckdbMapSubscriptTransform(SubscriptingRef *sbsref, List **indirection, struct ParseState *pstate, bool is_slice, + bool is_assignment) { +#else +DuckdbMapSubscriptTransform(SubscriptingRef *sbsref, List *indirection_, struct ParseState *pstate, bool is_slice, bool is_assignment) { + List **indirection = &indirection_; +#endif DuckdbSubscriptTransform(sbsref, indirection, pstate, is_slice, is_assignment, "duckdb.map"); } diff --git a/src/pgduckdb_ruleutils.cpp b/src/pgduckdb_ruleutils.cpp index 848cd468..725573f0 100644 --- a/src/pgduckdb_ruleutils.cpp +++ b/src/pgduckdb_ruleutils.cpp @@ -350,6 +350,14 @@ pgduckdb_subscript_has_custom_alias(Plan *plan, List *rtable, Var *subscript_var int varno; int varattno; + if (strcmp(colname, "?column?") == 0) { + /* + * If the column name is "?column?", then it means that Postgres + * couldn't figure out a decent alias. + */ + return false; + } + /* * If we have a syntactic referent for the Var, and we're working from a * parse tree, prefer to use the syntactic referent. Otherwise, fall back @@ -388,6 +396,15 @@ pgduckdb_strip_first_subscript(SubscriptingRef *sbsref, StringInfo buf) { } Assert(sbsref->refupperindexpr); + + if (linitial(sbsref->refupperindexpr) == NULL) { + return sbsref; + } + + if (IsA(linitial(sbsref->refupperindexpr), String)) { + return sbsref; + } + Oid typoutput; bool typIsVarlena; Const *constval = castNode(Const, linitial(sbsref->refupperindexpr)); diff --git a/src/vendor/pg_ruleutils_18.c b/src/vendor/pg_ruleutils_18.c index abb9bf2d..85c68f2a 100644 --- a/src/vendor/pg_ruleutils_18.c +++ b/src/vendor/pg_ruleutils_18.c @@ -13082,17 +13082,33 @@ printSubscripts(SubscriptingRef *sbsref, deparse_context *context) lowlist_item = list_head(sbsref->reflowerindexpr); /* could be NULL */ foreach(uplist_item, sbsref->refupperindexpr) { - appendStringInfoChar(buf, '['); - if (lowlist_item) + Node *up = (Node *) lfirst(uplist_item); + + if (!up) + { + appendStringInfoString(buf, ".*"); + } + else if (IsA(up, String)) + { + appendStringInfoChar(buf, '.'); + appendStringInfoString(buf, quote_identifier(strVal(up))); + } + else { + appendStringInfoChar(buf, '['); + if (lowlist_item) + { + /* If subexpression is NULL, get_rule_expr prints nothing */ + get_rule_expr((Node *) lfirst(lowlist_item), context, false); + appendStringInfoChar(buf, ':'); + } /* If subexpression is NULL, get_rule_expr prints nothing */ - get_rule_expr((Node *) lfirst(lowlist_item), context, false); - appendStringInfoChar(buf, ':'); - lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item); + get_rule_expr((Node *) lfirst(uplist_item), context, false); + appendStringInfoChar(buf, ']'); } - /* If subexpression is NULL, get_rule_expr prints nothing */ - get_rule_expr((Node *) lfirst(uplist_item), context, false); - appendStringInfoChar(buf, ']'); + + if (lowlist_item) + lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item); } } diff --git a/test/pycheck/motherduck_test.py b/test/pycheck/motherduck_test.py index 1d13f734..8617dadb 100644 --- a/test/pycheck/motherduck_test.py +++ b/test/pycheck/motherduck_test.py @@ -280,29 +280,36 @@ def test_md_duckdb_only_types(md_cur: Cursor, ddb: Duckdb): ddb.sql(""" CREATE TABLE t1( m MAP(INT, VARCHAR), + ms MAP(VARCHAR, INT), s STRUCT(v VARCHAR, i INTEGER), u UNION(t time, d date), )""") ddb.sql(""" INSERT INTO t1 VALUES ( MAP{1: 'abc'}, + MAP{'abc': 1}, {'v': 'struct abc', 'i': 123}, '12:00'::time, ), ( MAP{2: 'def'}, + MAP{'def': 2}, {'v': 'struct def', 'i': 456}, '2023-10-01'::date, ) """) md_cur.wait_until_table_exists("t1") assert md_cur.sql("""select * from t1""") == [ - ("{1=abc}", "{'v': struct abc, 'i': 123}", "12:00:00"), - ("{2=def}", "{'v': struct def, 'i': 456}", "2023-10-01"), + ("{1=abc}", "{abc=1}", "{'v': struct abc, 'i': 123}", "12:00:00"), + ("{2=def}", "{def=2}", "{'v': struct def, 'i': 456}", "2023-10-01"), ] assert md_cur.sql("""select m[1] from t1""") == ["abc", None] + assert md_cur.sql("""select ms['abc'] from t1""") == [1, None] + assert md_cur.sql("""select (ms).abc from t1""") == [1, None] assert md_cur.sql("""select s['v'] from t1""") == ["struct abc", "struct def"] assert md_cur.sql("""select s['i'] from t1""") == [123, 456] + assert md_cur.sql("""select (s).v from t1""") == ["struct abc", "struct def"] + assert md_cur.sql("""select (s).i from t1""") == [123, 456] assert md_cur.sql("""select union_extract(u,'t') from t1""") == [ datetime.time(12, 0), None, diff --git a/test/regression/expected/json_functions_duckdb.out b/test/regression/expected/json_functions_duckdb.out index b22b52fe..1b4d0eb0 100644 --- a/test/regression/expected/json_functions_duckdb.out +++ b/test/regression/expected/json_functions_duckdb.out @@ -343,6 +343,15 @@ SELECT public.json_transform(j, '{"family": "VARCHAR", "coolness": "DOUBLE"}') F {'family': canidae, 'coolness': NULL} (2 rows) +SELECT (transformed).* FROM ( + SELECT public.json_transform(j, '{"family": "VARCHAR", "coolness": "DOUBLE"}') as transformed FROM example2 +) q; + family | coolness +----------+---------- + anatidae | 42.42 + canidae | +(2 rows) + SELECT public.json_transform(j, '{"family": "TINYINT", "coolness": "DECIMAL(4, 2)"}') FROM example2; json_transform ------------------------------------- diff --git a/test/regression/expected/read_functions.out b/test/regression/expected/read_functions.out index 2d104342..8dba7f3c 100644 --- a/test/regression/expected/read_functions.out +++ b/test/regression/expected/read_functions.out @@ -63,20 +63,15 @@ SELECT arraycol[1:2] FROM ( (1 row) SELECT r['arraycol'][:] FROM read_parquet('../../data/indexable.parquet') r; - r.arraycol[:] ---------------- - {11,22,33} -(1 row) +ERROR: (PGDuckDB/CreatePlan) Prepared query returned an error: 'Parser Error: syntax error at or near "*" +LINE 1: SELECT r.arraycol.* FROM system.main.read_parquet('../../data/indexable.parquet... + ^ SELECT arraycol[:] FROM ( SELECT r['arraycol'] arraycol FROM read_parquet('../../data/indexable.parquet') r ) q; - arraycol ------------- - {11,22,33} -(1 row) - +ERROR: (PGDuckDB/CreatePlan) Prepared query returned an error: 'Binder Error: Cannot extract field from expression "arraycol.*" because it is not a struct -- Subqueries correctly expand *, in case of multiple columns. SELECT * FROM ( SELECT 'something' as prefix, *, 'something else' as postfix @@ -506,7 +501,7 @@ SELECT COUNT(r['a']) FROM read_json('../../data/table.json') r WHERE r['c'] > 50 51 (1 row) -SELECT r['a'], r['b'], r['c'] FROM read_json('../../data/table.json') r WHERE r['c'] > 50.4 AND r['c'] < 51.2; +SELECT (r).a, (r).b, (r).c FROM read_json('../../data/table.json') r WHERE (r).c > 50.4 AND (r).c < 51.2; a | b | c ----+---------+------ 50 | json_50 | 50.5 diff --git a/test/regression/expected/unresolved_type.out b/test/regression/expected/unresolved_type.out index e1ccb586..254ba721 100644 --- a/test/regression/expected/unresolved_type.out +++ b/test/regression/expected/unresolved_type.out @@ -203,3 +203,9 @@ select make_timestamptz(r['microseconds']) from duckdb.query($$ SELECT 168657000 Mon Jun 12 04:40:00 2023 PDT (1 row) +SELECT (s).* FROM (select (r).s FROM duckdb.query($$ SELECT {'key1': 'value1', 'key2': 42} AS s $$) r); + key1 | key2 +--------+------ + value1 | 42 +(1 row) + diff --git a/test/regression/sql/json_functions_duckdb.sql b/test/regression/sql/json_functions_duckdb.sql index 999c159e..0b19b0b6 100644 --- a/test/regression/sql/json_functions_duckdb.sql +++ b/test/regression/sql/json_functions_duckdb.sql @@ -212,6 +212,9 @@ SELECT public.json_group_structure(j) FROM example2; -- ('{"family": "canidae", "species": ["labrador", "bulldog"], "hair": true}'); -- -- SELECT public.json_transform(j, '{"family": "VARCHAR", "coolness": "DOUBLE"}') FROM example2; +SELECT (transformed).* FROM ( + SELECT public.json_transform(j, '{"family": "VARCHAR", "coolness": "DOUBLE"}') as transformed FROM example2 +) q; SELECT public.json_transform(j, '{"family": "TINYINT", "coolness": "DECIMAL(4, 2)"}') FROM example2; diff --git a/test/regression/sql/read_functions.sql b/test/regression/sql/read_functions.sql index 59031d4b..3c4fc646 100644 --- a/test/regression/sql/read_functions.sql +++ b/test/regression/sql/read_functions.sql @@ -284,4 +284,4 @@ SELECT * FROM iceberg_metadata('../../data/lineitem_iceberg', allow_moved_paths SELECT COUNT(r['a']) FROM read_json('../../data/table.json') r; SELECT COUNT(r['a']) FROM read_json('../../data/table.json') r WHERE r['c'] > 50.4; -SELECT r['a'], r['b'], r['c'] FROM read_json('../../data/table.json') r WHERE r['c'] > 50.4 AND r['c'] < 51.2; +SELECT (r).a, (r).b, (r).c FROM read_json('../../data/table.json') r WHERE (r).c > 50.4 AND (r).c < 51.2; diff --git a/test/regression/sql/unresolved_type.sql b/test/regression/sql/unresolved_type.sql index 1307a8e4..03f7c1a1 100644 --- a/test/regression/sql/unresolved_type.sql +++ b/test/regression/sql/unresolved_type.sql @@ -48,3 +48,5 @@ select make_timestamp(1686570000000000); select make_timestamp(r['microseconds']) from duckdb.query($$ SELECT 1686570000000000 AS microseconds $$) r; select make_timestamptz(1686570000000000); select make_timestamptz(r['microseconds']) from duckdb.query($$ SELECT 1686570000000000 AS microseconds $$) r; + +SELECT (s).* FROM (select (r).s FROM duckdb.query($$ SELECT {'key1': 'value1', 'key2': 42} AS s $$) r);