From 3cbd3d1a5845462520c9c5f811b41d541fd8f12b Mon Sep 17 00:00:00 2001 From: Jianghua Yang Date: Tue, 31 Mar 2026 01:12:09 +0800 Subject: [PATCH 1/6] Revert "Fix COPY TO returning 0 rows during concurrent reorganize" This reverts commit f97979911465650b6626eeffa49af9429a11d2c6. --- .../pax/copy_to_concurrent_reorganize.out | 289 ------ .../src/test/isolation2/isolation2_schedule | 1 - .../sql/pax/copy_to_concurrent_reorganize.sql | 170 ---- src/backend/commands/copy.c | 81 -- src/backend/commands/copyto.c | 37 - .../copy_to_concurrent_reorganize.out | 918 ------------------ src/test/isolation2/isolation2_schedule | 1 - .../sql/copy_to_concurrent_reorganize.sql | 561 ----------- 8 files changed, 2058 deletions(-) delete mode 100644 contrib/pax_storage/src/test/isolation2/expected/pax/copy_to_concurrent_reorganize.out delete mode 100644 contrib/pax_storage/src/test/isolation2/sql/pax/copy_to_concurrent_reorganize.sql delete mode 100644 src/test/isolation2/expected/copy_to_concurrent_reorganize.out delete mode 100644 src/test/isolation2/sql/copy_to_concurrent_reorganize.sql diff --git a/contrib/pax_storage/src/test/isolation2/expected/pax/copy_to_concurrent_reorganize.out b/contrib/pax_storage/src/test/isolation2/expected/pax/copy_to_concurrent_reorganize.out deleted file mode 100644 index b4beed7d035..00000000000 --- a/contrib/pax_storage/src/test/isolation2/expected/pax/copy_to_concurrent_reorganize.out +++ /dev/null @@ -1,289 +0,0 @@ --- Test: PAX table — relation-based COPY TO concurrent with ALTER TABLE SET WITH (reorganize=true) --- Issue: https://github.com/apache/cloudberry/issues/1545 --- Same as test 2.1 in the main isolation2 suite but for PAX storage. - -CREATE TABLE copy_reorg_pax_test (a INT, b INT) DISTRIBUTED BY (a); -CREATE -INSERT INTO copy_reorg_pax_test SELECT i, i FROM generate_series(1, 1000) i; -INSERT 1000 - --- Record original row count -SELECT count(*) FROM copy_reorg_pax_test; - count -------- - 1000 -(1 row) - --- Session 1: Begin reorganize (holds AccessExclusiveLock) -1: BEGIN; -BEGIN -1: ALTER TABLE copy_reorg_pax_test SET WITH (reorganize=true); -ALTER - --- Session 2: relation-based COPY TO should block on AccessShareLock -2&: COPY copy_reorg_pax_test TO '/tmp/copy_reorg_pax_test.csv'; - --- Confirm Session 2 is waiting for the lock -1: SELECT count(*) > 0 FROM pg_stat_activity WHERE query LIKE 'COPY copy_reorg_pax_test%' AND wait_event_type = 'Lock'; - ?column? ----------- - t -(1 row) - --- Session 1: Commit reorganize, releasing AccessExclusiveLock -1: COMMIT; -COMMIT - --- Session 2: Should return 1000 rows (fixed), not 0 rows (broken) -2<: <... completed> -COPY 1000 - --- Verify the output file contains all rows -CREATE TABLE copy_reorg_pax_verify (a INT, b INT) DISTRIBUTED BY (a); -CREATE -COPY copy_reorg_pax_verify FROM '/tmp/copy_reorg_pax_test.csv'; -COPY 1000 -SELECT count(*) FROM copy_reorg_pax_verify; - count -------- - 1000 -(1 row) - --- Cleanup -DROP TABLE copy_reorg_pax_verify; -DROP -DROP TABLE copy_reorg_pax_test; -DROP - --- ============================================================ --- Test 2.2c: PAX — query-based COPY TO + concurrent reorganize --- Fixed: BeginCopy() refreshes snapshot after AcquireRewriteLocks(). --- ============================================================ - -CREATE TABLE copy_query_reorg_pax_test (a INT, b INT) DISTRIBUTED BY (a); -CREATE -INSERT INTO copy_query_reorg_pax_test SELECT i, i FROM generate_series(1, 1000) i; -INSERT 1000 - -SELECT count(*) FROM copy_query_reorg_pax_test; - count -------- - 1000 -(1 row) - -1: BEGIN; -BEGIN -1: ALTER TABLE copy_query_reorg_pax_test SET WITH (reorganize=true); -ALTER - -2&: COPY (SELECT * FROM copy_query_reorg_pax_test) TO '/tmp/copy_query_reorg_pax_test.csv'; - -1: SELECT count(*) > 0 FROM pg_stat_activity WHERE query LIKE 'COPY (SELECT%copy_query_reorg_pax_test%' AND wait_event_type = 'Lock'; - ?column? ----------- - t -(1 row) - -1: COMMIT; -COMMIT -2<: <... completed> -COPY 1000 - -CREATE TABLE copy_query_reorg_pax_verify (a INT, b INT) DISTRIBUTED BY (a); -CREATE -COPY copy_query_reorg_pax_verify FROM '/tmp/copy_query_reorg_pax_test.csv'; -COPY 1000 -SELECT count(*) FROM copy_query_reorg_pax_verify; - count -------- - 1000 -(1 row) - -DROP TABLE copy_query_reorg_pax_verify; -DROP -DROP TABLE copy_query_reorg_pax_test; -DROP - --- ============================================================ --- Test 2.3c: PAX — partitioned table COPY TO + child partition concurrent reorganize --- Fixed: DoCopy() calls find_all_inheritors() to lock all child partitions first. --- ============================================================ - -CREATE TABLE copy_part_parent_pax (a INT, b INT) PARTITION BY RANGE (a) DISTRIBUTED BY (a); -CREATE -CREATE TABLE copy_part_child1_pax PARTITION OF copy_part_parent_pax FOR VALUES FROM (1) TO (501); -CREATE -CREATE TABLE copy_part_child2_pax PARTITION OF copy_part_parent_pax FOR VALUES FROM (501) TO (1001); -CREATE -INSERT INTO copy_part_parent_pax SELECT i, i FROM generate_series(1, 1000) i; -INSERT 1000 - -SELECT count(*) FROM copy_part_parent_pax; - count -------- - 1000 -(1 row) - -1: BEGIN; -BEGIN -1: ALTER TABLE copy_part_child1_pax SET WITH (reorganize=true); -ALTER - -2&: COPY copy_part_parent_pax TO '/tmp/copy_part_parent_pax.csv'; - -1: SELECT count(*) > 0 FROM pg_stat_activity WHERE query LIKE 'COPY copy_part_parent_pax%' AND wait_event_type = 'Lock'; - ?column? ----------- - t -(1 row) - -1: COMMIT; -COMMIT -2<: <... completed> -COPY 1000 - -CREATE TABLE copy_part_pax_verify (a INT, b INT) DISTRIBUTED BY (a); -CREATE -COPY copy_part_pax_verify FROM '/tmp/copy_part_parent_pax.csv'; -COPY 1000 -SELECT count(*) FROM copy_part_pax_verify; - count -------- - 1000 -(1 row) - -DROP TABLE copy_part_pax_verify; -DROP -DROP TABLE copy_part_parent_pax; -DROP - --- ============================================================ --- Test 2.4c: PAX — RLS table COPY TO + policy-referenced table concurrent reorganize --- Fixed: same as 2.2c — BeginCopy() refreshes snapshot after AcquireRewriteLocks(). --- ============================================================ - -CREATE TABLE copy_rls_pax_lookup (cat INT) DISTRIBUTED BY (cat); -CREATE -INSERT INTO copy_rls_pax_lookup SELECT i FROM generate_series(1, 2) i; -INSERT 2 - -CREATE TABLE copy_rls_pax_main (a INT, category INT) DISTRIBUTED BY (a); -CREATE -INSERT INTO copy_rls_pax_main SELECT i, (i % 5) + 1 FROM generate_series(1, 1000) i; -INSERT 1000 - -ALTER TABLE copy_rls_pax_main ENABLE ROW LEVEL SECURITY; -ALTER -CREATE POLICY p_rls_pax ON copy_rls_pax_main USING (category IN (SELECT cat from copy_rls_pax_lookup)); -CREATE - -CREATE ROLE copy_rls_pax_testuser; -CREATE -GRANT pg_write_server_files TO copy_rls_pax_testuser; -GRANT -GRANT ALL ON copy_rls_pax_main TO copy_rls_pax_testuser; -GRANT -GRANT ALL ON copy_rls_pax_lookup TO copy_rls_pax_testuser; -GRANT - -SELECT count(*) FROM copy_rls_pax_main; - count -------- - 1000 -(1 row) - -2: SET ROLE copy_rls_pax_testuser; COPY copy_rls_pax_main TO '/tmp/copy_rls_pax_main.csv'; -SET 400 - -1: BEGIN; -BEGIN -1: ALTER TABLE copy_rls_pax_lookup SET WITH (reorganize=true); -ALTER - -2&: SET ROLE copy_rls_pax_testuser; COPY copy_rls_pax_main TO '/tmp/copy_rls_pax_main.csv'; - -1: SELECT count(*) > 0 FROM pg_stat_activity WHERE query LIKE '%COPY copy_rls_pax_main%' AND wait_event_type = 'Lock'; - ?column? ----------- - t -(1 row) - -1: COMMIT; -COMMIT -2<: <... completed> -SET 400 - --- Reset session 2's role to avoid leaking to subsequent tests -2: RESET ROLE; -RESET - -RESET ROLE; -RESET -CREATE TABLE copy_rls_pax_verify (a INT, category INT) DISTRIBUTED BY (a); -CREATE -COPY copy_rls_pax_verify FROM '/tmp/copy_rls_pax_main.csv'; -COPY 400 -SELECT count(*) FROM copy_rls_pax_verify; - count -------- - 400 -(1 row) - -DROP TABLE copy_rls_pax_verify; -DROP -DROP POLICY p_rls_pax ON copy_rls_pax_main; -DROP -DROP TABLE copy_rls_pax_main; -DROP -DROP TABLE copy_rls_pax_lookup; -DROP -DROP ROLE copy_rls_pax_testuser; -DROP - --- ============================================================ --- Test 2.5c: PAX — CTAS + concurrent reorganize --- Fixed as a side effect via BeginCopy() snapshot refresh. --- ============================================================ - -CREATE TABLE ctas_reorg_pax_src (a INT, b INT) DISTRIBUTED BY (a); -CREATE -INSERT INTO ctas_reorg_pax_src SELECT i, i FROM generate_series(1, 1000) i; -INSERT 1000 - -SELECT count(*) FROM ctas_reorg_pax_src; - count -------- - 1000 -(1 row) - -1: BEGIN; -BEGIN -1: ALTER TABLE ctas_reorg_pax_src SET WITH (reorganize=true); -ALTER - -2&: CREATE TABLE ctas_reorg_pax_dst AS SELECT * FROM ctas_reorg_pax_src DISTRIBUTED BY (a); - -1: SELECT count(*) > 0 FROM pg_stat_activity WHERE query LIKE 'CREATE TABLE ctas_reorg_pax_dst%' AND wait_event_type = 'Lock'; - ?column? ----------- - t -(1 row) - -1: COMMIT; -COMMIT -2<: <... completed> -CREATE 1000 - -SELECT count(*) FROM ctas_reorg_pax_dst; - count -------- - 1000 -(1 row) - -DROP TABLE ctas_reorg_pax_dst; -DROP -DROP TABLE ctas_reorg_pax_src; -DROP - --- NOTE: Test 2.6c (PAX variant of change distribution key + query-based COPY TO) --- removed for the same reason as test 2.6 (server crash, pre-existing bug). diff --git a/contrib/pax_storage/src/test/isolation2/isolation2_schedule b/contrib/pax_storage/src/test/isolation2/isolation2_schedule index fa163aa96b6..72fa06f5204 100644 --- a/contrib/pax_storage/src/test/isolation2/isolation2_schedule +++ b/contrib/pax_storage/src/test/isolation2/isolation2_schedule @@ -157,7 +157,6 @@ test: pax/vacuum_while_vacuum # test: uao/bad_buffer_on_temp_ao_row test: reorganize_after_ao_vacuum_skip_drop truncate_after_ao_vacuum_skip_drop mark_all_aoseg_await_drop -test: pax/copy_to_concurrent_reorganize # below test(s) inject faults so each of them need to be in a separate group test: segwalrep/master_wal_switch diff --git a/contrib/pax_storage/src/test/isolation2/sql/pax/copy_to_concurrent_reorganize.sql b/contrib/pax_storage/src/test/isolation2/sql/pax/copy_to_concurrent_reorganize.sql deleted file mode 100644 index 05ef25852e9..00000000000 --- a/contrib/pax_storage/src/test/isolation2/sql/pax/copy_to_concurrent_reorganize.sql +++ /dev/null @@ -1,170 +0,0 @@ --- Test: PAX table — relation-based COPY TO concurrent with ALTER TABLE SET WITH (reorganize=true) --- Issue: https://github.com/apache/cloudberry/issues/1545 --- Same as test 2.1 in the main isolation2 suite but for PAX storage. - -CREATE TABLE copy_reorg_pax_test (a INT, b INT) DISTRIBUTED BY (a); -INSERT INTO copy_reorg_pax_test SELECT i, i FROM generate_series(1, 1000) i; - --- Record original row count -SELECT count(*) FROM copy_reorg_pax_test; - --- Session 1: Begin reorganize (holds AccessExclusiveLock) -1: BEGIN; -1: ALTER TABLE copy_reorg_pax_test SET WITH (reorganize=true); - --- Session 2: relation-based COPY TO should block on AccessShareLock -2&: COPY copy_reorg_pax_test TO '/tmp/copy_reorg_pax_test.csv'; - --- Confirm Session 2 is waiting for the lock -1: SELECT count(*) > 0 FROM pg_stat_activity - WHERE query LIKE 'COPY copy_reorg_pax_test%' AND wait_event_type = 'Lock'; - --- Session 1: Commit reorganize, releasing AccessExclusiveLock -1: COMMIT; - --- Session 2: Should return 1000 rows (fixed), not 0 rows (broken) -2<: - --- Verify the output file contains all rows -CREATE TABLE copy_reorg_pax_verify (a INT, b INT) DISTRIBUTED BY (a); -COPY copy_reorg_pax_verify FROM '/tmp/copy_reorg_pax_test.csv'; -SELECT count(*) FROM copy_reorg_pax_verify; - --- Cleanup -DROP TABLE copy_reorg_pax_verify; -DROP TABLE copy_reorg_pax_test; - --- ============================================================ --- Test 2.2c: PAX — query-based COPY TO + concurrent reorganize --- Fixed: BeginCopy() refreshes snapshot after AcquireRewriteLocks(). --- ============================================================ - -CREATE TABLE copy_query_reorg_pax_test (a INT, b INT) DISTRIBUTED BY (a); -INSERT INTO copy_query_reorg_pax_test SELECT i, i FROM generate_series(1, 1000) i; - -SELECT count(*) FROM copy_query_reorg_pax_test; - -1: BEGIN; -1: ALTER TABLE copy_query_reorg_pax_test SET WITH (reorganize=true); - -2&: COPY (SELECT * FROM copy_query_reorg_pax_test) TO '/tmp/copy_query_reorg_pax_test.csv'; - -1: SELECT count(*) > 0 FROM pg_stat_activity - WHERE query LIKE 'COPY (SELECT%copy_query_reorg_pax_test%' AND wait_event_type = 'Lock'; - -1: COMMIT; -2<: - -CREATE TABLE copy_query_reorg_pax_verify (a INT, b INT) DISTRIBUTED BY (a); -COPY copy_query_reorg_pax_verify FROM '/tmp/copy_query_reorg_pax_test.csv'; -SELECT count(*) FROM copy_query_reorg_pax_verify; - -DROP TABLE copy_query_reorg_pax_verify; -DROP TABLE copy_query_reorg_pax_test; - --- ============================================================ --- Test 2.3c: PAX — partitioned table COPY TO + child partition concurrent reorganize --- Fixed: DoCopy() calls find_all_inheritors() to lock all child partitions first. --- ============================================================ - -CREATE TABLE copy_part_parent_pax (a INT, b INT) PARTITION BY RANGE (a) DISTRIBUTED BY (a); -CREATE TABLE copy_part_child1_pax PARTITION OF copy_part_parent_pax FOR VALUES FROM (1) TO (501); -CREATE TABLE copy_part_child2_pax PARTITION OF copy_part_parent_pax FOR VALUES FROM (501) TO (1001); -INSERT INTO copy_part_parent_pax SELECT i, i FROM generate_series(1, 1000) i; - -SELECT count(*) FROM copy_part_parent_pax; - -1: BEGIN; -1: ALTER TABLE copy_part_child1_pax SET WITH (reorganize=true); - -2&: COPY copy_part_parent_pax TO '/tmp/copy_part_parent_pax.csv'; - -1: SELECT count(*) > 0 FROM pg_stat_activity - WHERE query LIKE 'COPY copy_part_parent_pax%' AND wait_event_type = 'Lock'; - -1: COMMIT; -2<: - -CREATE TABLE copy_part_pax_verify (a INT, b INT) DISTRIBUTED BY (a); -COPY copy_part_pax_verify FROM '/tmp/copy_part_parent_pax.csv'; -SELECT count(*) FROM copy_part_pax_verify; - -DROP TABLE copy_part_pax_verify; -DROP TABLE copy_part_parent_pax; - --- ============================================================ --- Test 2.4c: PAX — RLS table COPY TO + policy-referenced table concurrent reorganize --- Fixed: same as 2.2c — BeginCopy() refreshes snapshot after AcquireRewriteLocks(). --- ============================================================ - -CREATE TABLE copy_rls_pax_lookup (cat INT) DISTRIBUTED BY (cat); -INSERT INTO copy_rls_pax_lookup SELECT i FROM generate_series(1, 2) i; - -CREATE TABLE copy_rls_pax_main (a INT, category INT) DISTRIBUTED BY (a); -INSERT INTO copy_rls_pax_main SELECT i, (i % 5) + 1 FROM generate_series(1, 1000) i; - -ALTER TABLE copy_rls_pax_main ENABLE ROW LEVEL SECURITY; -CREATE POLICY p_rls_pax ON copy_rls_pax_main USING (category IN (SELECT cat from copy_rls_pax_lookup)); - -CREATE ROLE copy_rls_pax_testuser; -GRANT pg_write_server_files TO copy_rls_pax_testuser; -GRANT ALL ON copy_rls_pax_main TO copy_rls_pax_testuser; -GRANT ALL ON copy_rls_pax_lookup TO copy_rls_pax_testuser; - -SELECT count(*) FROM copy_rls_pax_main; - -2: SET ROLE copy_rls_pax_testuser; COPY copy_rls_pax_main TO '/tmp/copy_rls_pax_main.csv'; - -1: BEGIN; -1: ALTER TABLE copy_rls_pax_lookup SET WITH (reorganize=true); - -2&: SET ROLE copy_rls_pax_testuser; COPY copy_rls_pax_main TO '/tmp/copy_rls_pax_main.csv'; - -1: SELECT count(*) > 0 FROM pg_stat_activity - WHERE query LIKE '%COPY copy_rls_pax_main%' AND wait_event_type = 'Lock'; - -1: COMMIT; -2<: - --- Reset session 2's role to avoid leaking to subsequent tests -2: RESET ROLE; - -RESET ROLE; -CREATE TABLE copy_rls_pax_verify (a INT, category INT) DISTRIBUTED BY (a); -COPY copy_rls_pax_verify FROM '/tmp/copy_rls_pax_main.csv'; -SELECT count(*) FROM copy_rls_pax_verify; - -DROP TABLE copy_rls_pax_verify; -DROP POLICY p_rls_pax ON copy_rls_pax_main; -DROP TABLE copy_rls_pax_main; -DROP TABLE copy_rls_pax_lookup; -DROP ROLE copy_rls_pax_testuser; - --- ============================================================ --- Test 2.5c: PAX — CTAS + concurrent reorganize --- Fixed as a side effect via BeginCopy() snapshot refresh. --- ============================================================ - -CREATE TABLE ctas_reorg_pax_src (a INT, b INT) DISTRIBUTED BY (a); -INSERT INTO ctas_reorg_pax_src SELECT i, i FROM generate_series(1, 1000) i; - -SELECT count(*) FROM ctas_reorg_pax_src; - -1: BEGIN; -1: ALTER TABLE ctas_reorg_pax_src SET WITH (reorganize=true); - -2&: CREATE TABLE ctas_reorg_pax_dst AS SELECT * FROM ctas_reorg_pax_src DISTRIBUTED BY (a); - -1: SELECT count(*) > 0 FROM pg_stat_activity - WHERE query LIKE 'CREATE TABLE ctas_reorg_pax_dst%' AND wait_event_type = 'Lock'; - -1: COMMIT; -2<: - -SELECT count(*) FROM ctas_reorg_pax_dst; - -DROP TABLE ctas_reorg_pax_dst; -DROP TABLE ctas_reorg_pax_src; - --- NOTE: Test 2.6c (PAX variant of change distribution key + query-based COPY TO) --- removed for the same reason as test 2.6 (server crash, pre-existing bug). diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index c9d2ac4f968..4ccd3798067 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -58,7 +58,6 @@ #include "catalog/catalog.h" #include "catalog/gp_matview_aux.h" #include "catalog/namespace.h" -#include "catalog/pg_inherits.h" #include "catalog/pg_extprotocol.h" #include "cdb/cdbappendonlyam.h" #include "cdb/cdbaocsam.h" @@ -137,37 +136,6 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt, { /* Open and lock the relation, using the appropriate lock type. */ rel = table_openrv(stmt->relation, lockmode); - - /* - * For COPY TO, refresh the active snapshot after acquiring the lock. - * - * The snapshot was originally pushed by PortalRunUtility() before - * DoCopy() was called, which means it was taken before we acquired - * the lock on the relation. If we had to wait for a conflicting lock - * (e.g., AccessExclusiveLock held by a concurrent ALTER TABLE ... - * SET WITH (reorganize=true)), the snapshot may predate the - * concurrent transaction's commit. After the lock is granted, scanning - * with such a stale snapshot would miss all tuples written by the - * concurrent transaction, resulting in COPY returning zero rows. - * - * This mirrors the approach used by exec_simple_query() for SELECT - * statements, which pops the parse/analyze snapshot and takes a fresh - * one in PortalStart() after locks have been acquired (see the comment - * at postgres.c:1859-1867). It is also consistent with how VACUUM and - * CLUSTER manage their own snapshots internally. - * - * In REPEATABLE READ or SERIALIZABLE mode, GetTransactionSnapshot() - * returns the same transaction-level snapshot regardless, making this - * a harmless no-op. - * - * We only do this for COPY TO (!is_from) because COPY FROM inserts - * data and does not scan existing tuples with a snapshot. - */ - if (!is_from && ActiveSnapshotSet()) - { - PopActiveSnapshot(); - PushActiveSnapshot(GetTransactionSnapshot()); - } } /* @@ -304,55 +272,6 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt, errmsg("COPY FROM not supported with row-level security"), errhint("Use INSERT statements instead."))); - /* - * For partitioned table COPY TO: eagerly acquire AccessShareLock - * on all child partitions before refreshing the snapshot. - * - * When COPY is performed on a partitioned table, the parent - * relation's AccessShareLock is acquired above (via table_openrv) - * and Method A already refreshed the snapshot. However, the - * parent's AccessShareLock does NOT conflict with an - * AccessExclusiveLock held on a child partition by a concurrent - * reorganize. As a result, Method A's snapshot may still predate - * the child's reorganize commit. - * - * Child partition locks are acquired later, deep inside - * ExecutorStart() via ExecInitAppend(), by which time the snapshot - * has already been embedded in the QueryDesc via - * PushCopiedSnapshot() in BeginCopy(). Even a second snapshot - * refresh in BeginCopy() (after AcquireRewriteLocks) would not - * help, because AcquireRewriteLocks only locks the parent (child - * partitions are not in the initial range table of - * "SELECT * FROM parent"). - * - * The fix: call find_all_inheritors() with AccessShareLock to - * acquire locks on every child partition NOW, before building the - * query. If a child partition's reorganize holds - * AccessExclusiveLock, this call blocks until that transaction - * commits. Once it returns, all child-level reorganize operations - * have committed, and a fresh snapshot taken here will see all - * reorganized child data. - * - * find_all_inheritors() acquires locks that persist to end of - * transaction. The executor will re-acquire them during scan - * initialization, which is a lock-manager no-op. - */ - if (!is_from && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) - { - List *part_oids; - - part_oids = find_all_inheritors(RelationGetRelid(rel), - AccessShareLock, NULL); - list_free(part_oids); - - /* Refresh snapshot: all child partition locks now held */ - if (ActiveSnapshotSet()) - { - PopActiveSnapshot(); - PushActiveSnapshot(GetTransactionSnapshot()); - } - } - /* * Build target list * diff --git a/src/backend/commands/copyto.c b/src/backend/commands/copyto.c index 88e61305250..871a973235e 100644 --- a/src/backend/commands/copyto.c +++ b/src/backend/commands/copyto.c @@ -1198,43 +1198,6 @@ BeginCopy(ParseState *pstate, Assert(query->utilityStmt == NULL); - /* - * Refresh the active snapshot after pg_analyze_and_rewrite() has - * acquired all necessary relation locks via AcquireRewriteLocks(). - * - * The snapshot in use was pushed by PortalRunUtility() before DoCopy() - * was called -- before any table locks were acquired. If - * AcquireRewriteLocks() had to wait for a conflicting - * AccessExclusiveLock (e.g., held by a concurrent ALTER TABLE ... - * SET WITH (reorganize=true)), the lock wait is now over and the - * reorganize transaction has committed. The snapshot taken before the - * wait does not reflect that commit: after reorganize completes, - * swap_relation_files() has replaced the physical storage, so old - * tuples no longer exist and the new tuples have xmin = reorganize_xid - * which is not yet visible in the pre-wait snapshot. Scanning with - * the stale snapshot returns 0 rows -- a violation of transaction - * atomicity (the reader must see either all old rows or all new rows). - * - * By refreshing the snapshot here -- after all locks are acquired -- - * we guarantee that the query will see the committed post-reorganize - * data. - * - * This applies to: - * - Pure query-based COPY TO: COPY (SELECT ...) TO - * - RLS table COPY TO: converted to query-based in DoCopy(); the - * RLS policy references an external lookup table whose lock is - * acquired by AcquireRewriteLocks(). - * - * In REPEATABLE READ or SERIALIZABLE isolation, - * GetTransactionSnapshot() returns the same transaction-level - * snapshot, making this a harmless no-op. - */ - if (ActiveSnapshotSet()) - { - PopActiveSnapshot(); - PushActiveSnapshot(GetTransactionSnapshot()); - } - /* * Similarly the grammar doesn't enforce the presence of a RETURNING * clause, but this is required here. diff --git a/src/test/isolation2/expected/copy_to_concurrent_reorganize.out b/src/test/isolation2/expected/copy_to_concurrent_reorganize.out deleted file mode 100644 index 0a7dfd38801..00000000000 --- a/src/test/isolation2/expected/copy_to_concurrent_reorganize.out +++ /dev/null @@ -1,918 +0,0 @@ --- Test: COPY TO concurrent with ALTER TABLE SET WITH (reorganize=true) --- Issue: https://github.com/apache/cloudberry/issues/1545 --- --- Tests 2.1: Core fix (relation-based COPY TO) --- Tests 2.2-2.5: Extended fixes for query-based, partitioned, RLS, and CTAS paths - --- ============================================================ --- Test 2.1: relation-based COPY TO + concurrent reorganize --- Reproduces issue #1545: COPY TO should return correct row count --- after waiting for reorganize to release AccessExclusiveLock. --- ============================================================ - -CREATE TABLE copy_reorg_test (a INT, b INT) DISTRIBUTED BY (a); -CREATE -INSERT INTO copy_reorg_test SELECT i, i FROM generate_series(1, 1000) i; -INSERT 1000 - --- Record original row count -SELECT count(*) FROM copy_reorg_test; - count -------- - 1000 -(1 row) - --- Session 1: Begin reorganize (holds AccessExclusiveLock) -1: BEGIN; -BEGIN -1: ALTER TABLE copy_reorg_test SET WITH (reorganize=true); -ALTER - --- Session 2: relation-based COPY TO should block on AccessShareLock --- At this point PortalRunUtility has already acquired a snapshot (before reorganize commits), --- then DoCopy tries to acquire the lock and blocks. -2&: COPY copy_reorg_test TO '/tmp/copy_reorg_test.csv'; - --- Confirm Session 2 is waiting for the lock -1: SELECT count(*) > 0 FROM pg_stat_activity WHERE query LIKE 'COPY copy_reorg_test%' AND wait_event_type = 'Lock'; - ?column? ----------- - t -(1 row) - --- Session 1: Commit reorganize, releasing AccessExclusiveLock -1: COMMIT; -COMMIT - --- Session 2: Should return 1000 rows (fixed), not 0 rows (broken) -2<: <... completed> -COPY 1000 - --- Verify the output file contains all rows -CREATE TABLE copy_reorg_verify (a INT, b INT) DISTRIBUTED BY (a); -CREATE -COPY copy_reorg_verify FROM '/tmp/copy_reorg_test.csv'; -COPY 1000 -SELECT count(*) FROM copy_reorg_verify; - count -------- - 1000 -(1 row) - --- Cleanup -DROP TABLE copy_reorg_verify; -DROP -DROP TABLE copy_reorg_test; -DROP - --- ============================================================ --- Test 2.2: query-based COPY TO + concurrent reorganize --- Fixed: BeginCopy() refreshes snapshot after pg_analyze_and_rewrite() --- acquires all relation locks via AcquireRewriteLocks(). --- ============================================================ - -CREATE TABLE copy_query_reorg_test (a INT, b INT) DISTRIBUTED BY (a); -CREATE -INSERT INTO copy_query_reorg_test SELECT i, i FROM generate_series(1, 1000) i; -INSERT 1000 - -SELECT count(*) FROM copy_query_reorg_test; - count -------- - 1000 -(1 row) - --- Session 1: reorganize holds AccessExclusiveLock -1: BEGIN; -BEGIN -1: ALTER TABLE copy_query_reorg_test SET WITH (reorganize=true); -ALTER - --- Session 2: query-based COPY TO blocks (lock acquired in pg_analyze_and_rewrite -> AcquireRewriteLocks) -2&: COPY (SELECT * FROM copy_query_reorg_test) TO '/tmp/copy_query_reorg_test.csv'; - --- Confirm Session 2 is blocked -1: SELECT count(*) > 0 FROM pg_stat_activity WHERE query LIKE 'COPY (SELECT%copy_query_reorg_test%' AND wait_event_type = 'Lock'; - ?column? ----------- - t -(1 row) - --- Session 1: Commit -1: COMMIT; -COMMIT - --- Session 2: Complete -2<: <... completed> -COPY 1000 - --- Verify the output file contains all rows -CREATE TABLE copy_query_reorg_verify (a INT, b INT) DISTRIBUTED BY (a); -CREATE -COPY copy_query_reorg_verify FROM '/tmp/copy_query_reorg_test.csv'; -COPY 1000 -SELECT count(*) FROM copy_query_reorg_verify; - count -------- - 1000 -(1 row) - --- Cleanup -DROP TABLE copy_query_reorg_verify; -DROP -DROP TABLE copy_query_reorg_test; -DROP - --- ============================================================ --- Test 2.3: partitioned table COPY TO + child partition concurrent reorganize --- Fixed: DoCopy() calls find_all_inheritors() to eagerly lock all child --- partitions before refreshing the snapshot, ensuring the snapshot sees all --- child reorganize commits before the query is built. --- ============================================================ - -CREATE TABLE copy_part_parent (a INT, b INT) PARTITION BY RANGE (a) DISTRIBUTED BY (a); -CREATE -CREATE TABLE copy_part_child1 PARTITION OF copy_part_parent FOR VALUES FROM (1) TO (501); -CREATE -CREATE TABLE copy_part_child2 PARTITION OF copy_part_parent FOR VALUES FROM (501) TO (1001); -CREATE -INSERT INTO copy_part_parent SELECT i, i FROM generate_series(1, 1000) i; -INSERT 1000 - -SELECT count(*) FROM copy_part_parent; - count -------- - 1000 -(1 row) - --- Session 1: reorganize the child partition -1: BEGIN; -BEGIN -1: ALTER TABLE copy_part_child1 SET WITH (reorganize=true); -ALTER - --- Session 2: COPY parent TO (internally converted to query-based, child lock acquired in analyze phase) -2&: COPY copy_part_parent TO '/tmp/copy_part_parent.csv'; - --- Confirm Session 2 is blocked -1: SELECT count(*) > 0 FROM pg_stat_activity WHERE query LIKE 'COPY copy_part_parent%' AND wait_event_type = 'Lock'; - ?column? ----------- - t -(1 row) - --- Session 1: Commit -1: COMMIT; -COMMIT - --- Session 2: Complete -2<: <... completed> -COPY 1000 - --- Verify the output file contains all rows -CREATE TABLE copy_part_verify (a INT, b INT) DISTRIBUTED BY (a); -CREATE -COPY copy_part_verify FROM '/tmp/copy_part_parent.csv'; -COPY 1000 -SELECT count(*) FROM copy_part_verify; - count -------- - 1000 -(1 row) - --- Cleanup -DROP TABLE copy_part_verify; -DROP -DROP TABLE copy_part_parent; -DROP - --- ============================================================ --- Test 2.4: RLS table COPY TO + policy-referenced table concurrent reorganize --- Fixed: same as 2.2 — BeginCopy() refreshes snapshot after AcquireRewriteLocks() --- which also acquires the lock on the RLS policy's lookup table. --- ============================================================ - -CREATE TABLE copy_rls_lookup (cat INT) DISTRIBUTED BY (cat); -CREATE -INSERT INTO copy_rls_lookup SELECT i FROM generate_series(1, 2) i; -INSERT 2 - -CREATE TABLE copy_rls_main (a INT, category INT) DISTRIBUTED BY (a); -CREATE -INSERT INTO copy_rls_main SELECT i, (i % 5) + 1 FROM generate_series(1, 1000) i; -INSERT 1000 - -ALTER TABLE copy_rls_main ENABLE ROW LEVEL SECURITY; -ALTER -CREATE POLICY p_rls ON copy_rls_main USING (category IN (SELECT cat FROM copy_rls_lookup)); -CREATE - --- Create non-superuser to trigger RLS (needs pg_write_server_files to COPY TO file) -CREATE ROLE copy_rls_testuser; -CREATE -GRANT pg_write_server_files TO copy_rls_testuser; -GRANT -GRANT ALL ON copy_rls_main TO copy_rls_testuser; -GRANT -GRANT ALL ON copy_rls_lookup TO copy_rls_testuser; -GRANT - -SELECT count(*) FROM copy_rls_main; - count -------- - 1000 -(1 row) - --- Baseline: verify RLS filters correctly (should return 400 rows: categories 1 and 2 only) -2: SET ROLE copy_rls_testuser; COPY copy_rls_main TO '/tmp/copy_rls_main.csv'; -SET 400 - --- Session 1: reorganize the lookup table -1: BEGIN; -BEGIN -1: ALTER TABLE copy_rls_lookup SET WITH (reorganize=true); -ALTER - --- Session 2: COPY TO as non-superuser (RLS active, internally converted to query-based) -2&: SET ROLE copy_rls_testuser; COPY copy_rls_main TO '/tmp/copy_rls_main.csv'; - --- Confirm Session 2 is blocked -1: SELECT count(*) > 0 FROM pg_stat_activity WHERE query LIKE '%COPY copy_rls_main%' AND wait_event_type = 'Lock'; - ?column? ----------- - t -(1 row) - --- Session 1: Commit -1: COMMIT; -COMMIT - --- Session 2: Complete -2<: <... completed> -SET 400 - --- Reset session 2's role to avoid leaking to subsequent tests -2: RESET ROLE; -RESET - --- Verify: should match baseline count (400 rows filtered by RLS) -RESET ROLE; -RESET -CREATE TABLE copy_rls_verify (a INT, category INT) DISTRIBUTED BY (a); -CREATE -COPY copy_rls_verify FROM '/tmp/copy_rls_main.csv'; -COPY 400 -SELECT count(*) FROM copy_rls_verify; - count -------- - 400 -(1 row) - --- Cleanup -DROP TABLE copy_rls_verify; -DROP -DROP POLICY p_rls ON copy_rls_main; -DROP -DROP TABLE copy_rls_main; -DROP -DROP TABLE copy_rls_lookup; -DROP -DROP ROLE copy_rls_testuser; -DROP - --- ============================================================ --- Test 2.5: CTAS + concurrent reorganize --- Fixed as a side effect: CTAS goes through pg_analyze_and_rewrite() + --- AcquireRewriteLocks(), so the snapshot refresh in BeginCopy() also fixes it. --- ============================================================ - -CREATE TABLE ctas_reorg_src (a INT, b INT) DISTRIBUTED BY (a); -CREATE -INSERT INTO ctas_reorg_src SELECT i, i FROM generate_series(1, 1000) i; -INSERT 1000 - -SELECT count(*) FROM ctas_reorg_src; - count -------- - 1000 -(1 row) - --- Session 1: reorganize -1: BEGIN; -BEGIN -1: ALTER TABLE ctas_reorg_src SET WITH (reorganize=true); -ALTER - --- Session 2: CTAS should block (lock acquired in executor or analyze phase) -2&: CREATE TABLE ctas_reorg_dst AS SELECT * FROM ctas_reorg_src DISTRIBUTED BY (a); - --- Confirm Session 2 is blocked -1: SELECT count(*) > 0 FROM pg_stat_activity WHERE query LIKE 'CREATE TABLE ctas_reorg_dst%' AND wait_event_type = 'Lock'; - ?column? ----------- - t -(1 row) - --- Session 1: Commit -1: COMMIT; -COMMIT - --- Session 2: Complete -2<: <... completed> -CREATE 1000 - --- Verify row count after CTAS completes -SELECT count(*) FROM ctas_reorg_dst; - count -------- - 1000 -(1 row) - --- Cleanup -DROP TABLE ctas_reorg_dst; -DROP -DROP TABLE ctas_reorg_src; -DROP - --- NOTE: Test 2.6 (change distribution key + query-based COPY TO) removed because --- ALTER TABLE SET DISTRIBUTED BY + concurrent query-based COPY TO causes a server --- crash (pre-existing Cloudberry bug, not related to this fix). - --- ============================================================ --- Test 2.1a: AO row table — relation-based COPY TO + concurrent reorganize --- Same as 2.1 but using append-optimized row-oriented table. --- ============================================================ - -CREATE TABLE copy_reorg_ao_row_test (a INT, b INT) USING ao_row DISTRIBUTED BY (a); -CREATE -INSERT INTO copy_reorg_ao_row_test SELECT i, i FROM generate_series(1, 1000) i; -INSERT 1000 - --- Record original row count -SELECT count(*) FROM copy_reorg_ao_row_test; - count -------- - 1000 -(1 row) - --- Session 1: Begin reorganize (holds AccessExclusiveLock) -1: BEGIN; -BEGIN -1: ALTER TABLE copy_reorg_ao_row_test SET WITH (reorganize=true); -ALTER - --- Session 2: relation-based COPY TO should block on AccessShareLock -2&: COPY copy_reorg_ao_row_test TO '/tmp/copy_reorg_ao_row_test.csv'; - --- Confirm Session 2 is waiting for the lock -1: SELECT count(*) > 0 FROM pg_stat_activity WHERE query LIKE 'COPY copy_reorg_ao_row_test%' AND wait_event_type = 'Lock'; - ?column? ----------- - t -(1 row) - --- Session 1: Commit reorganize, releasing AccessExclusiveLock -1: COMMIT; -COMMIT - --- Session 2: Should return 1000 rows (fixed), not 0 rows (broken) -2<: <... completed> -COPY 1000 - --- Verify the output file contains all rows -CREATE TABLE copy_reorg_ao_row_verify (a INT, b INT) USING ao_row DISTRIBUTED BY (a); -CREATE -COPY copy_reorg_ao_row_verify FROM '/tmp/copy_reorg_ao_row_test.csv'; -COPY 1000 -SELECT count(*) FROM copy_reorg_ao_row_verify; - count -------- - 1000 -(1 row) - --- Cleanup -DROP TABLE copy_reorg_ao_row_verify; -DROP -DROP TABLE copy_reorg_ao_row_test; -DROP - --- ============================================================ --- Test 2.1b: AO column table — relation-based COPY TO + concurrent reorganize --- Same as 2.1 but using append-optimized column-oriented table. --- ============================================================ - -CREATE TABLE copy_reorg_ao_col_test (a INT, b INT) USING ao_column DISTRIBUTED BY (a); -CREATE -INSERT INTO copy_reorg_ao_col_test SELECT i, i FROM generate_series(1, 1000) i; -INSERT 1000 - --- Record original row count -SELECT count(*) FROM copy_reorg_ao_col_test; - count -------- - 1000 -(1 row) - --- Session 1: Begin reorganize (holds AccessExclusiveLock) -1: BEGIN; -BEGIN -1: ALTER TABLE copy_reorg_ao_col_test SET WITH (reorganize=true); -ALTER - --- Session 2: relation-based COPY TO should block on AccessShareLock -2&: COPY copy_reorg_ao_col_test TO '/tmp/copy_reorg_ao_col_test.csv'; - --- Confirm Session 2 is waiting for the lock -1: SELECT count(*) > 0 FROM pg_stat_activity WHERE query LIKE 'COPY copy_reorg_ao_col_test%' AND wait_event_type = 'Lock'; - ?column? ----------- - t -(1 row) - --- Session 1: Commit reorganize, releasing AccessExclusiveLock -1: COMMIT; -COMMIT - --- Session 2: Should return 1000 rows (fixed), not 0 rows (broken) -2<: <... completed> -COPY 1000 - --- Verify the output file contains all rows -CREATE TABLE copy_reorg_ao_col_verify (a INT, b INT) USING ao_column DISTRIBUTED BY (a); -CREATE -COPY copy_reorg_ao_col_verify FROM '/tmp/copy_reorg_ao_col_test.csv'; -COPY 1000 -SELECT count(*) FROM copy_reorg_ao_col_verify; - count -------- - 1000 -(1 row) - --- Cleanup -DROP TABLE copy_reorg_ao_col_verify; -DROP -DROP TABLE copy_reorg_ao_col_test; -DROP - --- ============================================================ --- Test 2.2a: AO row — query-based COPY TO + concurrent reorganize --- Fixed: BeginCopy() refreshes snapshot after AcquireRewriteLocks(). --- ============================================================ - -CREATE TABLE copy_query_reorg_ao_row_test (a INT, b INT) USING ao_row DISTRIBUTED BY (a); -CREATE -INSERT INTO copy_query_reorg_ao_row_test SELECT i, i FROM generate_series(1, 1000) i; -INSERT 1000 - -SELECT count(*) FROM copy_query_reorg_ao_row_test; - count -------- - 1000 -(1 row) - -1: BEGIN; -BEGIN -1: ALTER TABLE copy_query_reorg_ao_row_test SET WITH (reorganize=true); -ALTER - -2&: COPY (SELECT * FROM copy_query_reorg_ao_row_test) TO '/tmp/copy_query_reorg_ao_row_test.csv'; - -1: SELECT count(*) > 0 FROM pg_stat_activity WHERE query LIKE 'COPY (SELECT%copy_query_reorg_ao_row_test%' AND wait_event_type = 'Lock'; - ?column? ----------- - t -(1 row) - -1: COMMIT; -COMMIT -2<: <... completed> -COPY 1000 - -CREATE TABLE copy_query_reorg_ao_row_verify (a INT, b INT) USING ao_row DISTRIBUTED BY (a); -CREATE -COPY copy_query_reorg_ao_row_verify FROM '/tmp/copy_query_reorg_ao_row_test.csv'; -COPY 1000 -SELECT count(*) FROM copy_query_reorg_ao_row_verify; - count -------- - 1000 -(1 row) - -DROP TABLE copy_query_reorg_ao_row_verify; -DROP -DROP TABLE copy_query_reorg_ao_row_test; -DROP - --- ============================================================ --- Test 2.2b: AO column — query-based COPY TO + concurrent reorganize --- Fixed: BeginCopy() refreshes snapshot after AcquireRewriteLocks(). --- ============================================================ - -CREATE TABLE copy_query_reorg_ao_col_test (a INT, b INT) USING ao_column DISTRIBUTED BY (a); -CREATE -INSERT INTO copy_query_reorg_ao_col_test SELECT i, i FROM generate_series(1, 1000) i; -INSERT 1000 - -SELECT count(*) FROM copy_query_reorg_ao_col_test; - count -------- - 1000 -(1 row) - -1: BEGIN; -BEGIN -1: ALTER TABLE copy_query_reorg_ao_col_test SET WITH (reorganize=true); -ALTER - -2&: COPY (SELECT * FROM copy_query_reorg_ao_col_test) TO '/tmp/copy_query_reorg_ao_col_test.csv'; - -1: SELECT count(*) > 0 FROM pg_stat_activity WHERE query LIKE 'COPY (SELECT%copy_query_reorg_ao_col_test%' AND wait_event_type = 'Lock'; - ?column? ----------- - t -(1 row) - -1: COMMIT; -COMMIT -2<: <... completed> -COPY 1000 - -CREATE TABLE copy_query_reorg_ao_col_verify (a INT, b INT) USING ao_column DISTRIBUTED BY (a); -CREATE -COPY copy_query_reorg_ao_col_verify FROM '/tmp/copy_query_reorg_ao_col_test.csv'; -COPY 1000 -SELECT count(*) FROM copy_query_reorg_ao_col_verify; - count -------- - 1000 -(1 row) - -DROP TABLE copy_query_reorg_ao_col_verify; -DROP -DROP TABLE copy_query_reorg_ao_col_test; -DROP - --- ============================================================ --- Test 2.3a: AO row — partitioned table COPY TO + child partition concurrent reorganize --- Fixed: DoCopy() calls find_all_inheritors() to lock all child partitions first. --- ============================================================ - -CREATE TABLE copy_part_parent_ao_row (a INT, b INT) PARTITION BY RANGE (a) DISTRIBUTED BY (a); -CREATE -CREATE TABLE copy_part_child1_ao_row PARTITION OF copy_part_parent_ao_row FOR VALUES FROM (1) TO (501) USING ao_row; -CREATE -CREATE TABLE copy_part_child2_ao_row PARTITION OF copy_part_parent_ao_row FOR VALUES FROM (501) TO (1001) USING ao_row; -CREATE -INSERT INTO copy_part_parent_ao_row SELECT i, i FROM generate_series(1, 1000) i; -INSERT 1000 - -SELECT count(*) FROM copy_part_parent_ao_row; - count -------- - 1000 -(1 row) - -1: BEGIN; -BEGIN -1: ALTER TABLE copy_part_child1_ao_row SET WITH (reorganize=true); -ALTER - -2&: COPY copy_part_parent_ao_row TO '/tmp/copy_part_parent_ao_row.csv'; - -1: SELECT count(*) > 0 FROM pg_stat_activity WHERE query LIKE 'COPY copy_part_parent_ao_row%' AND wait_event_type = 'Lock'; - ?column? ----------- - t -(1 row) - -1: COMMIT; -COMMIT -2<: <... completed> -COPY 1000 - -CREATE TABLE copy_part_ao_row_verify (a INT, b INT) USING ao_row DISTRIBUTED BY (a); -CREATE -COPY copy_part_ao_row_verify FROM '/tmp/copy_part_parent_ao_row.csv'; -COPY 1000 -SELECT count(*) FROM copy_part_ao_row_verify; - count -------- - 1000 -(1 row) - -DROP TABLE copy_part_ao_row_verify; -DROP -DROP TABLE copy_part_parent_ao_row; -DROP - --- ============================================================ --- Test 2.3b: AO column — partitioned table COPY TO + child partition concurrent reorganize --- Fixed: DoCopy() calls find_all_inheritors() to lock all child partitions first. --- ============================================================ - -CREATE TABLE copy_part_parent_ao_col (a INT, b INT) PARTITION BY RANGE (a) DISTRIBUTED BY (a); -CREATE -CREATE TABLE copy_part_child1_ao_col PARTITION OF copy_part_parent_ao_col FOR VALUES FROM (1) TO (501) USING ao_column; -CREATE -CREATE TABLE copy_part_child2_ao_col PARTITION OF copy_part_parent_ao_col FOR VALUES FROM (501) TO (1001) USING ao_column; -CREATE -INSERT INTO copy_part_parent_ao_col SELECT i, i FROM generate_series(1, 1000) i; -INSERT 1000 - -SELECT count(*) FROM copy_part_parent_ao_col; - count -------- - 1000 -(1 row) - -1: BEGIN; -BEGIN -1: ALTER TABLE copy_part_child1_ao_col SET WITH (reorganize=true); -ALTER - -2&: COPY copy_part_parent_ao_col TO '/tmp/copy_part_parent_ao_col.csv'; - -1: SELECT count(*) > 0 FROM pg_stat_activity WHERE query LIKE 'COPY copy_part_parent_ao_col%' AND wait_event_type = 'Lock'; - ?column? ----------- - t -(1 row) - -1: COMMIT; -COMMIT -2<: <... completed> -COPY 1000 - -CREATE TABLE copy_part_ao_col_verify (a INT, b INT) USING ao_column DISTRIBUTED BY (a); -CREATE -COPY copy_part_ao_col_verify FROM '/tmp/copy_part_parent_ao_col.csv'; -COPY 1000 -SELECT count(*) FROM copy_part_ao_col_verify; - count -------- - 1000 -(1 row) - -DROP TABLE copy_part_ao_col_verify; -DROP -DROP TABLE copy_part_parent_ao_col; -DROP - --- ============================================================ --- Test 2.4a: AO row — RLS table COPY TO + policy-referenced table concurrent reorganize --- Fixed: same as 2.4 — BeginCopy() refreshes snapshot after AcquireRewriteLocks(). --- ============================================================ - -CREATE TABLE copy_rls_ao_row_lookup (cat INT) USING ao_row DISTRIBUTED BY (cat); -CREATE -INSERT INTO copy_rls_ao_row_lookup SELECT i FROM generate_series(1, 2) i; -INSERT 2 - -CREATE TABLE copy_rls_ao_row_main (a INT, category INT) USING ao_row DISTRIBUTED BY (a); -CREATE -INSERT INTO copy_rls_ao_row_main SELECT i, (i % 5) + 1 FROM generate_series(1, 1000) i; -INSERT 1000 - -ALTER TABLE copy_rls_ao_row_main ENABLE ROW LEVEL SECURITY; -ALTER -CREATE POLICY p_rls_ao_row ON copy_rls_ao_row_main USING (category IN (SELECT cat FROM copy_rls_ao_row_lookup)); -CREATE - -CREATE ROLE copy_rls_ao_row_testuser; -CREATE -GRANT pg_write_server_files TO copy_rls_ao_row_testuser; -GRANT -GRANT ALL ON copy_rls_ao_row_main TO copy_rls_ao_row_testuser; -GRANT -GRANT ALL ON copy_rls_ao_row_lookup TO copy_rls_ao_row_testuser; -GRANT - -SELECT count(*) FROM copy_rls_ao_row_main; - count -------- - 1000 -(1 row) - --- Baseline: verify RLS filters correctly (should return 400 rows: categories 1 and 2 only) -2: SET ROLE copy_rls_ao_row_testuser; COPY copy_rls_ao_row_main TO '/tmp/copy_rls_ao_row_main.csv'; -SET 400 - -1: BEGIN; -BEGIN -1: ALTER TABLE copy_rls_ao_row_lookup SET WITH (reorganize=true); -ALTER - -2&: SET ROLE copy_rls_ao_row_testuser; COPY copy_rls_ao_row_main TO '/tmp/copy_rls_ao_row_main.csv'; - -1: SELECT count(*) > 0 FROM pg_stat_activity WHERE query LIKE '%COPY copy_rls_ao_row_main%' AND wait_event_type = 'Lock'; - ?column? ----------- - t -(1 row) - -1: COMMIT; -COMMIT -2<: <... completed> -SET 400 - -2: RESET ROLE; -RESET - -RESET ROLE; -RESET -CREATE TABLE copy_rls_ao_row_verify (a INT, category INT) USING ao_row DISTRIBUTED BY (a); -CREATE -COPY copy_rls_ao_row_verify FROM '/tmp/copy_rls_ao_row_main.csv'; -COPY 400 -SELECT count(*) FROM copy_rls_ao_row_verify; - count -------- - 400 -(1 row) - -DROP TABLE copy_rls_ao_row_verify; -DROP -DROP POLICY p_rls_ao_row ON copy_rls_ao_row_main; -DROP -DROP TABLE copy_rls_ao_row_main; -DROP -DROP TABLE copy_rls_ao_row_lookup; -DROP -DROP ROLE copy_rls_ao_row_testuser; -DROP - --- ============================================================ --- Test 2.4b: AO column — RLS table COPY TO + policy-referenced table concurrent reorganize --- Fixed: same as 2.4 — BeginCopy() refreshes snapshot after AcquireRewriteLocks(). --- ============================================================ - -CREATE TABLE copy_rls_ao_col_lookup (cat INT) USING ao_column DISTRIBUTED BY (cat); -CREATE -INSERT INTO copy_rls_ao_col_lookup SELECT i FROM generate_series(1, 2) i; -INSERT 2 - -CREATE TABLE copy_rls_ao_col_main (a INT, category INT) USING ao_column DISTRIBUTED BY (a); -CREATE -INSERT INTO copy_rls_ao_col_main SELECT i, (i % 5) + 1 FROM generate_series(1, 1000) i; -INSERT 1000 - -ALTER TABLE copy_rls_ao_col_main ENABLE ROW LEVEL SECURITY; -ALTER -CREATE POLICY p_rls_ao_col ON copy_rls_ao_col_main USING (category IN (SELECT cat FROM copy_rls_ao_col_lookup)); -CREATE - -CREATE ROLE copy_rls_ao_col_testuser; -CREATE -GRANT pg_write_server_files TO copy_rls_ao_col_testuser; -GRANT -GRANT ALL ON copy_rls_ao_col_main TO copy_rls_ao_col_testuser; -GRANT -GRANT ALL ON copy_rls_ao_col_lookup TO copy_rls_ao_col_testuser; -GRANT - -SELECT count(*) FROM copy_rls_ao_col_main; - count -------- - 1000 -(1 row) - --- Baseline: verify RLS filters correctly (should return 400 rows: categories 1 and 2 only) -2: SET ROLE copy_rls_ao_col_testuser; COPY copy_rls_ao_col_main TO '/tmp/copy_rls_ao_col_main.csv'; -SET 400 - -1: BEGIN; -BEGIN -1: ALTER TABLE copy_rls_ao_col_lookup SET WITH (reorganize=true); -ALTER - -2&: SET ROLE copy_rls_ao_col_testuser; COPY copy_rls_ao_col_main TO '/tmp/copy_rls_ao_col_main.csv'; - -1: SELECT count(*) > 0 FROM pg_stat_activity WHERE query LIKE '%COPY copy_rls_ao_col_main%' AND wait_event_type = 'Lock'; - ?column? ----------- - t -(1 row) - -1: COMMIT; -COMMIT -2<: <... completed> -SET 400 - -2: RESET ROLE; -RESET - -RESET ROLE; -RESET -CREATE TABLE copy_rls_ao_col_verify (a INT, category INT) USING ao_column DISTRIBUTED BY (a); -CREATE -COPY copy_rls_ao_col_verify FROM '/tmp/copy_rls_ao_col_main.csv'; -COPY 400 -SELECT count(*) FROM copy_rls_ao_col_verify; - count -------- - 400 -(1 row) - -DROP TABLE copy_rls_ao_col_verify; -DROP -DROP POLICY p_rls_ao_col ON copy_rls_ao_col_main; -DROP -DROP TABLE copy_rls_ao_col_main; -DROP -DROP TABLE copy_rls_ao_col_lookup; -DROP -DROP ROLE copy_rls_ao_col_testuser; -DROP - --- ============================================================ --- Test 2.5a: AO row — CTAS + concurrent reorganize --- Fixed as a side effect via BeginCopy() snapshot refresh. --- ============================================================ - -CREATE TABLE ctas_reorg_ao_row_src (a INT, b INT) USING ao_row DISTRIBUTED BY (a); -CREATE -INSERT INTO ctas_reorg_ao_row_src SELECT i, i FROM generate_series(1, 1000) i; -INSERT 1000 - -SELECT count(*) FROM ctas_reorg_ao_row_src; - count -------- - 1000 -(1 row) - -1: BEGIN; -BEGIN -1: ALTER TABLE ctas_reorg_ao_row_src SET WITH (reorganize=true); -ALTER - -2&: CREATE TABLE ctas_reorg_ao_row_dst AS SELECT * FROM ctas_reorg_ao_row_src DISTRIBUTED BY (a); - -1: SELECT count(*) > 0 FROM pg_stat_activity WHERE query LIKE 'CREATE TABLE ctas_reorg_ao_row_dst%' AND wait_event_type = 'Lock'; - ?column? ----------- - t -(1 row) - -1: COMMIT; -COMMIT -2<: <... completed> -CREATE 1000 - -SELECT count(*) FROM ctas_reorg_ao_row_dst; - count -------- - 1000 -(1 row) - -DROP TABLE ctas_reorg_ao_row_dst; -DROP -DROP TABLE ctas_reorg_ao_row_src; -DROP - --- ============================================================ --- Test 2.5b: AO column — CTAS + concurrent reorganize --- Fixed as a side effect via BeginCopy() snapshot refresh. --- ============================================================ - -CREATE TABLE ctas_reorg_ao_col_src (a INT, b INT) USING ao_column DISTRIBUTED BY (a); -CREATE -INSERT INTO ctas_reorg_ao_col_src SELECT i, i FROM generate_series(1, 1000) i; -INSERT 1000 - -SELECT count(*) FROM ctas_reorg_ao_col_src; - count -------- - 1000 -(1 row) - -1: BEGIN; -BEGIN -1: ALTER TABLE ctas_reorg_ao_col_src SET WITH (reorganize=true); -ALTER - -2&: CREATE TABLE ctas_reorg_ao_col_dst AS SELECT * FROM ctas_reorg_ao_col_src DISTRIBUTED BY (a); - -1: SELECT count(*) > 0 FROM pg_stat_activity WHERE query LIKE 'CREATE TABLE ctas_reorg_ao_col_dst%' AND wait_event_type = 'Lock'; - ?column? ----------- - t -(1 row) - -1: COMMIT; -COMMIT -2<: <... completed> -CREATE 1000 - -SELECT count(*) FROM ctas_reorg_ao_col_dst; - count -------- - 1000 -(1 row) - -DROP TABLE ctas_reorg_ao_col_dst; -DROP -DROP TABLE ctas_reorg_ao_col_src; -DROP - --- NOTE: Tests 2.6a/2.6b (AO variants of change distribution key + query-based COPY TO) --- removed for the same reason as test 2.6 (server crash, pre-existing bug). diff --git a/src/test/isolation2/isolation2_schedule b/src/test/isolation2/isolation2_schedule index 4a0f9dc6925..d9d33ad76e4 100644 --- a/src/test/isolation2/isolation2_schedule +++ b/src/test/isolation2/isolation2_schedule @@ -152,7 +152,6 @@ test: uao/fast_analyze_row test: uao/create_index_allows_readonly_row test: reorganize_after_ao_vacuum_skip_drop truncate_after_ao_vacuum_skip_drop mark_all_aoseg_await_drop -test: copy_to_concurrent_reorganize # below test(s) inject faults so each of them need to be in a separate group test: segwalrep/master_wal_switch diff --git a/src/test/isolation2/sql/copy_to_concurrent_reorganize.sql b/src/test/isolation2/sql/copy_to_concurrent_reorganize.sql deleted file mode 100644 index 3473193d142..00000000000 --- a/src/test/isolation2/sql/copy_to_concurrent_reorganize.sql +++ /dev/null @@ -1,561 +0,0 @@ --- Test: COPY TO concurrent with ALTER TABLE SET WITH (reorganize=true) --- Issue: https://github.com/apache/cloudberry/issues/1545 --- --- Tests 2.1: Core fix (relation-based COPY TO) --- Tests 2.2-2.5: Extended fixes for query-based, partitioned, RLS, and CTAS paths - --- ============================================================ --- Test 2.1: relation-based COPY TO + concurrent reorganize --- Reproduces issue #1545: COPY TO should return correct row count --- after waiting for reorganize to release AccessExclusiveLock. --- ============================================================ - -CREATE TABLE copy_reorg_test (a INT, b INT) DISTRIBUTED BY (a); -INSERT INTO copy_reorg_test SELECT i, i FROM generate_series(1, 1000) i; - --- Record original row count -SELECT count(*) FROM copy_reorg_test; - --- Session 1: Begin reorganize (holds AccessExclusiveLock) -1: BEGIN; -1: ALTER TABLE copy_reorg_test SET WITH (reorganize=true); - --- Session 2: relation-based COPY TO should block on AccessShareLock --- At this point PortalRunUtility has already acquired a snapshot (before reorganize commits), --- then DoCopy tries to acquire the lock and blocks. -2&: COPY copy_reorg_test TO '/tmp/copy_reorg_test.csv'; - --- Confirm Session 2 is waiting for the lock -1: SELECT count(*) > 0 FROM pg_stat_activity - WHERE query LIKE 'COPY copy_reorg_test%' AND wait_event_type = 'Lock'; - --- Session 1: Commit reorganize, releasing AccessExclusiveLock -1: COMMIT; - --- Session 2: Should return 1000 rows (fixed), not 0 rows (broken) -2<: - --- Verify the output file contains all rows -CREATE TABLE copy_reorg_verify (a INT, b INT) DISTRIBUTED BY (a); -COPY copy_reorg_verify FROM '/tmp/copy_reorg_test.csv'; -SELECT count(*) FROM copy_reorg_verify; - --- Cleanup -DROP TABLE copy_reorg_verify; -DROP TABLE copy_reorg_test; - --- ============================================================ --- Test 2.2: query-based COPY TO + concurrent reorganize --- Fixed: BeginCopy() refreshes snapshot after pg_analyze_and_rewrite() --- acquires all relation locks via AcquireRewriteLocks(). --- ============================================================ - -CREATE TABLE copy_query_reorg_test (a INT, b INT) DISTRIBUTED BY (a); -INSERT INTO copy_query_reorg_test SELECT i, i FROM generate_series(1, 1000) i; - -SELECT count(*) FROM copy_query_reorg_test; - --- Session 1: reorganize holds AccessExclusiveLock -1: BEGIN; -1: ALTER TABLE copy_query_reorg_test SET WITH (reorganize=true); - --- Session 2: query-based COPY TO blocks (lock acquired in pg_analyze_and_rewrite -> AcquireRewriteLocks) -2&: COPY (SELECT * FROM copy_query_reorg_test) TO '/tmp/copy_query_reorg_test.csv'; - --- Confirm Session 2 is blocked -1: SELECT count(*) > 0 FROM pg_stat_activity - WHERE query LIKE 'COPY (SELECT%copy_query_reorg_test%' AND wait_event_type = 'Lock'; - --- Session 1: Commit -1: COMMIT; - --- Session 2: Complete -2<: - --- Verify the output file contains all rows -CREATE TABLE copy_query_reorg_verify (a INT, b INT) DISTRIBUTED BY (a); -COPY copy_query_reorg_verify FROM '/tmp/copy_query_reorg_test.csv'; -SELECT count(*) FROM copy_query_reorg_verify; - --- Cleanup -DROP TABLE copy_query_reorg_verify; -DROP TABLE copy_query_reorg_test; - --- ============================================================ --- Test 2.3: partitioned table COPY TO + child partition concurrent reorganize --- Fixed: DoCopy() calls find_all_inheritors() to eagerly lock all child --- partitions before refreshing the snapshot, ensuring the snapshot sees all --- child reorganize commits before the query is built. --- ============================================================ - -CREATE TABLE copy_part_parent (a INT, b INT) PARTITION BY RANGE (a) DISTRIBUTED BY (a); -CREATE TABLE copy_part_child1 PARTITION OF copy_part_parent FOR VALUES FROM (1) TO (501); -CREATE TABLE copy_part_child2 PARTITION OF copy_part_parent FOR VALUES FROM (501) TO (1001); -INSERT INTO copy_part_parent SELECT i, i FROM generate_series(1, 1000) i; - -SELECT count(*) FROM copy_part_parent; - --- Session 1: reorganize the child partition -1: BEGIN; -1: ALTER TABLE copy_part_child1 SET WITH (reorganize=true); - --- Session 2: COPY parent TO (internally converted to query-based, child lock acquired in analyze phase) -2&: COPY copy_part_parent TO '/tmp/copy_part_parent.csv'; - --- Confirm Session 2 is blocked -1: SELECT count(*) > 0 FROM pg_stat_activity - WHERE query LIKE 'COPY copy_part_parent%' AND wait_event_type = 'Lock'; - --- Session 1: Commit -1: COMMIT; - --- Session 2: Complete -2<: - --- Verify the output file contains all rows -CREATE TABLE copy_part_verify (a INT, b INT) DISTRIBUTED BY (a); -COPY copy_part_verify FROM '/tmp/copy_part_parent.csv'; -SELECT count(*) FROM copy_part_verify; - --- Cleanup -DROP TABLE copy_part_verify; -DROP TABLE copy_part_parent; - --- ============================================================ --- Test 2.4: RLS table COPY TO + policy-referenced table concurrent reorganize --- Fixed: same as 2.2 — BeginCopy() refreshes snapshot after AcquireRewriteLocks() --- which also acquires the lock on the RLS policy's lookup table. --- ============================================================ - -CREATE TABLE copy_rls_lookup (cat INT) DISTRIBUTED BY (cat); -INSERT INTO copy_rls_lookup SELECT i FROM generate_series(1, 2) i; - -CREATE TABLE copy_rls_main (a INT, category INT) DISTRIBUTED BY (a); -INSERT INTO copy_rls_main SELECT i, (i % 5) + 1 FROM generate_series(1, 1000) i; - -ALTER TABLE copy_rls_main ENABLE ROW LEVEL SECURITY; -CREATE POLICY p_rls ON copy_rls_main USING (category IN (SELECT cat FROM copy_rls_lookup)); - --- Create non-superuser to trigger RLS (needs pg_write_server_files to COPY TO file) -CREATE ROLE copy_rls_testuser; -GRANT pg_write_server_files TO copy_rls_testuser; -GRANT ALL ON copy_rls_main TO copy_rls_testuser; -GRANT ALL ON copy_rls_lookup TO copy_rls_testuser; - -SELECT count(*) FROM copy_rls_main; - --- Baseline: verify RLS filters correctly (should return 400 rows: categories 1 and 2 only) -2: SET ROLE copy_rls_testuser; COPY copy_rls_main TO '/tmp/copy_rls_main.csv'; - --- Session 1: reorganize the lookup table -1: BEGIN; -1: ALTER TABLE copy_rls_lookup SET WITH (reorganize=true); - --- Session 2: COPY TO as non-superuser (RLS active, internally converted to query-based) -2&: SET ROLE copy_rls_testuser; COPY copy_rls_main TO '/tmp/copy_rls_main.csv'; - --- Confirm Session 2 is blocked -1: SELECT count(*) > 0 FROM pg_stat_activity - WHERE query LIKE '%COPY copy_rls_main%' AND wait_event_type = 'Lock'; - --- Session 1: Commit -1: COMMIT; - --- Session 2: Complete -2<: - --- Reset session 2's role to avoid leaking to subsequent tests -2: RESET ROLE; - --- Verify: should match baseline count (400 rows filtered by RLS) -RESET ROLE; -CREATE TABLE copy_rls_verify (a INT, category INT) DISTRIBUTED BY (a); -COPY copy_rls_verify FROM '/tmp/copy_rls_main.csv'; -SELECT count(*) FROM copy_rls_verify; - --- Cleanup -DROP TABLE copy_rls_verify; -DROP POLICY p_rls ON copy_rls_main; -DROP TABLE copy_rls_main; -DROP TABLE copy_rls_lookup; -DROP ROLE copy_rls_testuser; - --- ============================================================ --- Test 2.5: CTAS + concurrent reorganize --- Fixed as a side effect: CTAS goes through pg_analyze_and_rewrite() + --- AcquireRewriteLocks(), so the snapshot refresh in BeginCopy() also fixes it. --- ============================================================ - -CREATE TABLE ctas_reorg_src (a INT, b INT) DISTRIBUTED BY (a); -INSERT INTO ctas_reorg_src SELECT i, i FROM generate_series(1, 1000) i; - -SELECT count(*) FROM ctas_reorg_src; - --- Session 1: reorganize -1: BEGIN; -1: ALTER TABLE ctas_reorg_src SET WITH (reorganize=true); - --- Session 2: CTAS should block (lock acquired in executor or analyze phase) -2&: CREATE TABLE ctas_reorg_dst AS SELECT * FROM ctas_reorg_src DISTRIBUTED BY (a); - --- Confirm Session 2 is blocked -1: SELECT count(*) > 0 FROM pg_stat_activity - WHERE query LIKE 'CREATE TABLE ctas_reorg_dst%' AND wait_event_type = 'Lock'; - --- Session 1: Commit -1: COMMIT; - --- Session 2: Complete -2<: - --- Verify row count after CTAS completes -SELECT count(*) FROM ctas_reorg_dst; - --- Cleanup -DROP TABLE ctas_reorg_dst; -DROP TABLE ctas_reorg_src; - --- NOTE: Test 2.6 (change distribution key + query-based COPY TO) removed because --- ALTER TABLE SET DISTRIBUTED BY + concurrent query-based COPY TO causes a server --- crash (pre-existing Cloudberry bug, not related to this fix). - --- ============================================================ --- Test 2.1a: AO row table — relation-based COPY TO + concurrent reorganize --- Same as 2.1 but using append-optimized row-oriented table. --- ============================================================ - -CREATE TABLE copy_reorg_ao_row_test (a INT, b INT) USING ao_row DISTRIBUTED BY (a); -INSERT INTO copy_reorg_ao_row_test SELECT i, i FROM generate_series(1, 1000) i; - --- Record original row count -SELECT count(*) FROM copy_reorg_ao_row_test; - --- Session 1: Begin reorganize (holds AccessExclusiveLock) -1: BEGIN; -1: ALTER TABLE copy_reorg_ao_row_test SET WITH (reorganize=true); - --- Session 2: relation-based COPY TO should block on AccessShareLock -2&: COPY copy_reorg_ao_row_test TO '/tmp/copy_reorg_ao_row_test.csv'; - --- Confirm Session 2 is waiting for the lock -1: SELECT count(*) > 0 FROM pg_stat_activity - WHERE query LIKE 'COPY copy_reorg_ao_row_test%' AND wait_event_type = 'Lock'; - --- Session 1: Commit reorganize, releasing AccessExclusiveLock -1: COMMIT; - --- Session 2: Should return 1000 rows (fixed), not 0 rows (broken) -2<: - --- Verify the output file contains all rows -CREATE TABLE copy_reorg_ao_row_verify (a INT, b INT) USING ao_row DISTRIBUTED BY (a); -COPY copy_reorg_ao_row_verify FROM '/tmp/copy_reorg_ao_row_test.csv'; -SELECT count(*) FROM copy_reorg_ao_row_verify; - --- Cleanup -DROP TABLE copy_reorg_ao_row_verify; -DROP TABLE copy_reorg_ao_row_test; - --- ============================================================ --- Test 2.1b: AO column table — relation-based COPY TO + concurrent reorganize --- Same as 2.1 but using append-optimized column-oriented table. --- ============================================================ - -CREATE TABLE copy_reorg_ao_col_test (a INT, b INT) USING ao_column DISTRIBUTED BY (a); -INSERT INTO copy_reorg_ao_col_test SELECT i, i FROM generate_series(1, 1000) i; - --- Record original row count -SELECT count(*) FROM copy_reorg_ao_col_test; - --- Session 1: Begin reorganize (holds AccessExclusiveLock) -1: BEGIN; -1: ALTER TABLE copy_reorg_ao_col_test SET WITH (reorganize=true); - --- Session 2: relation-based COPY TO should block on AccessShareLock -2&: COPY copy_reorg_ao_col_test TO '/tmp/copy_reorg_ao_col_test.csv'; - --- Confirm Session 2 is waiting for the lock -1: SELECT count(*) > 0 FROM pg_stat_activity - WHERE query LIKE 'COPY copy_reorg_ao_col_test%' AND wait_event_type = 'Lock'; - --- Session 1: Commit reorganize, releasing AccessExclusiveLock -1: COMMIT; - --- Session 2: Should return 1000 rows (fixed), not 0 rows (broken) -2<: - --- Verify the output file contains all rows -CREATE TABLE copy_reorg_ao_col_verify (a INT, b INT) USING ao_column DISTRIBUTED BY (a); -COPY copy_reorg_ao_col_verify FROM '/tmp/copy_reorg_ao_col_test.csv'; -SELECT count(*) FROM copy_reorg_ao_col_verify; - --- Cleanup -DROP TABLE copy_reorg_ao_col_verify; -DROP TABLE copy_reorg_ao_col_test; - --- ============================================================ --- Test 2.2a: AO row — query-based COPY TO + concurrent reorganize --- Fixed: BeginCopy() refreshes snapshot after AcquireRewriteLocks(). --- ============================================================ - -CREATE TABLE copy_query_reorg_ao_row_test (a INT, b INT) USING ao_row DISTRIBUTED BY (a); -INSERT INTO copy_query_reorg_ao_row_test SELECT i, i FROM generate_series(1, 1000) i; - -SELECT count(*) FROM copy_query_reorg_ao_row_test; - -1: BEGIN; -1: ALTER TABLE copy_query_reorg_ao_row_test SET WITH (reorganize=true); - -2&: COPY (SELECT * FROM copy_query_reorg_ao_row_test) TO '/tmp/copy_query_reorg_ao_row_test.csv'; - -1: SELECT count(*) > 0 FROM pg_stat_activity - WHERE query LIKE 'COPY (SELECT%copy_query_reorg_ao_row_test%' AND wait_event_type = 'Lock'; - -1: COMMIT; -2<: - -CREATE TABLE copy_query_reorg_ao_row_verify (a INT, b INT) USING ao_row DISTRIBUTED BY (a); -COPY copy_query_reorg_ao_row_verify FROM '/tmp/copy_query_reorg_ao_row_test.csv'; -SELECT count(*) FROM copy_query_reorg_ao_row_verify; - -DROP TABLE copy_query_reorg_ao_row_verify; -DROP TABLE copy_query_reorg_ao_row_test; - --- ============================================================ --- Test 2.2b: AO column — query-based COPY TO + concurrent reorganize --- Fixed: BeginCopy() refreshes snapshot after AcquireRewriteLocks(). --- ============================================================ - -CREATE TABLE copy_query_reorg_ao_col_test (a INT, b INT) USING ao_column DISTRIBUTED BY (a); -INSERT INTO copy_query_reorg_ao_col_test SELECT i, i FROM generate_series(1, 1000) i; - -SELECT count(*) FROM copy_query_reorg_ao_col_test; - -1: BEGIN; -1: ALTER TABLE copy_query_reorg_ao_col_test SET WITH (reorganize=true); - -2&: COPY (SELECT * FROM copy_query_reorg_ao_col_test) TO '/tmp/copy_query_reorg_ao_col_test.csv'; - -1: SELECT count(*) > 0 FROM pg_stat_activity - WHERE query LIKE 'COPY (SELECT%copy_query_reorg_ao_col_test%' AND wait_event_type = 'Lock'; - -1: COMMIT; -2<: - -CREATE TABLE copy_query_reorg_ao_col_verify (a INT, b INT) USING ao_column DISTRIBUTED BY (a); -COPY copy_query_reorg_ao_col_verify FROM '/tmp/copy_query_reorg_ao_col_test.csv'; -SELECT count(*) FROM copy_query_reorg_ao_col_verify; - -DROP TABLE copy_query_reorg_ao_col_verify; -DROP TABLE copy_query_reorg_ao_col_test; - --- ============================================================ --- Test 2.3a: AO row — partitioned table COPY TO + child partition concurrent reorganize --- Fixed: DoCopy() calls find_all_inheritors() to lock all child partitions first. --- ============================================================ - -CREATE TABLE copy_part_parent_ao_row (a INT, b INT) PARTITION BY RANGE (a) DISTRIBUTED BY (a); -CREATE TABLE copy_part_child1_ao_row PARTITION OF copy_part_parent_ao_row FOR VALUES FROM (1) TO (501) USING ao_row; -CREATE TABLE copy_part_child2_ao_row PARTITION OF copy_part_parent_ao_row FOR VALUES FROM (501) TO (1001) USING ao_row; -INSERT INTO copy_part_parent_ao_row SELECT i, i FROM generate_series(1, 1000) i; - -SELECT count(*) FROM copy_part_parent_ao_row; - -1: BEGIN; -1: ALTER TABLE copy_part_child1_ao_row SET WITH (reorganize=true); - -2&: COPY copy_part_parent_ao_row TO '/tmp/copy_part_parent_ao_row.csv'; - -1: SELECT count(*) > 0 FROM pg_stat_activity - WHERE query LIKE 'COPY copy_part_parent_ao_row%' AND wait_event_type = 'Lock'; - -1: COMMIT; -2<: - -CREATE TABLE copy_part_ao_row_verify (a INT, b INT) USING ao_row DISTRIBUTED BY (a); -COPY copy_part_ao_row_verify FROM '/tmp/copy_part_parent_ao_row.csv'; -SELECT count(*) FROM copy_part_ao_row_verify; - -DROP TABLE copy_part_ao_row_verify; -DROP TABLE copy_part_parent_ao_row; - --- ============================================================ --- Test 2.3b: AO column — partitioned table COPY TO + child partition concurrent reorganize --- Fixed: DoCopy() calls find_all_inheritors() to lock all child partitions first. --- ============================================================ - -CREATE TABLE copy_part_parent_ao_col (a INT, b INT) PARTITION BY RANGE (a) DISTRIBUTED BY (a); -CREATE TABLE copy_part_child1_ao_col PARTITION OF copy_part_parent_ao_col FOR VALUES FROM (1) TO (501) USING ao_column; -CREATE TABLE copy_part_child2_ao_col PARTITION OF copy_part_parent_ao_col FOR VALUES FROM (501) TO (1001) USING ao_column; -INSERT INTO copy_part_parent_ao_col SELECT i, i FROM generate_series(1, 1000) i; - -SELECT count(*) FROM copy_part_parent_ao_col; - -1: BEGIN; -1: ALTER TABLE copy_part_child1_ao_col SET WITH (reorganize=true); - -2&: COPY copy_part_parent_ao_col TO '/tmp/copy_part_parent_ao_col.csv'; - -1: SELECT count(*) > 0 FROM pg_stat_activity - WHERE query LIKE 'COPY copy_part_parent_ao_col%' AND wait_event_type = 'Lock'; - -1: COMMIT; -2<: - -CREATE TABLE copy_part_ao_col_verify (a INT, b INT) USING ao_column DISTRIBUTED BY (a); -COPY copy_part_ao_col_verify FROM '/tmp/copy_part_parent_ao_col.csv'; -SELECT count(*) FROM copy_part_ao_col_verify; - -DROP TABLE copy_part_ao_col_verify; -DROP TABLE copy_part_parent_ao_col; - --- ============================================================ --- Test 2.4a: AO row — RLS table COPY TO + policy-referenced table concurrent reorganize --- Fixed: same as 2.4 — BeginCopy() refreshes snapshot after AcquireRewriteLocks(). --- ============================================================ - -CREATE TABLE copy_rls_ao_row_lookup (cat INT) USING ao_row DISTRIBUTED BY (cat); -INSERT INTO copy_rls_ao_row_lookup SELECT i FROM generate_series(1, 2) i; - -CREATE TABLE copy_rls_ao_row_main (a INT, category INT) USING ao_row DISTRIBUTED BY (a); -INSERT INTO copy_rls_ao_row_main SELECT i, (i % 5) + 1 FROM generate_series(1, 1000) i; - -ALTER TABLE copy_rls_ao_row_main ENABLE ROW LEVEL SECURITY; -CREATE POLICY p_rls_ao_row ON copy_rls_ao_row_main USING (category IN (SELECT cat FROM copy_rls_ao_row_lookup)); - -CREATE ROLE copy_rls_ao_row_testuser; -GRANT pg_write_server_files TO copy_rls_ao_row_testuser; -GRANT ALL ON copy_rls_ao_row_main TO copy_rls_ao_row_testuser; -GRANT ALL ON copy_rls_ao_row_lookup TO copy_rls_ao_row_testuser; - -SELECT count(*) FROM copy_rls_ao_row_main; - --- Baseline: verify RLS filters correctly (should return 400 rows: categories 1 and 2 only) -2: SET ROLE copy_rls_ao_row_testuser; COPY copy_rls_ao_row_main TO '/tmp/copy_rls_ao_row_main.csv'; - -1: BEGIN; -1: ALTER TABLE copy_rls_ao_row_lookup SET WITH (reorganize=true); - -2&: SET ROLE copy_rls_ao_row_testuser; COPY copy_rls_ao_row_main TO '/tmp/copy_rls_ao_row_main.csv'; - -1: SELECT count(*) > 0 FROM pg_stat_activity - WHERE query LIKE '%COPY copy_rls_ao_row_main%' AND wait_event_type = 'Lock'; - -1: COMMIT; -2<: - -2: RESET ROLE; - -RESET ROLE; -CREATE TABLE copy_rls_ao_row_verify (a INT, category INT) USING ao_row DISTRIBUTED BY (a); -COPY copy_rls_ao_row_verify FROM '/tmp/copy_rls_ao_row_main.csv'; -SELECT count(*) FROM copy_rls_ao_row_verify; - -DROP TABLE copy_rls_ao_row_verify; -DROP POLICY p_rls_ao_row ON copy_rls_ao_row_main; -DROP TABLE copy_rls_ao_row_main; -DROP TABLE copy_rls_ao_row_lookup; -DROP ROLE copy_rls_ao_row_testuser; - --- ============================================================ --- Test 2.4b: AO column — RLS table COPY TO + policy-referenced table concurrent reorganize --- Fixed: same as 2.4 — BeginCopy() refreshes snapshot after AcquireRewriteLocks(). --- ============================================================ - -CREATE TABLE copy_rls_ao_col_lookup (cat INT) USING ao_column DISTRIBUTED BY (cat); -INSERT INTO copy_rls_ao_col_lookup SELECT i FROM generate_series(1, 2) i; - -CREATE TABLE copy_rls_ao_col_main (a INT, category INT) USING ao_column DISTRIBUTED BY (a); -INSERT INTO copy_rls_ao_col_main SELECT i, (i % 5) + 1 FROM generate_series(1, 1000) i; - -ALTER TABLE copy_rls_ao_col_main ENABLE ROW LEVEL SECURITY; -CREATE POLICY p_rls_ao_col ON copy_rls_ao_col_main USING (category IN (SELECT cat FROM copy_rls_ao_col_lookup)); - -CREATE ROLE copy_rls_ao_col_testuser; -GRANT pg_write_server_files TO copy_rls_ao_col_testuser; -GRANT ALL ON copy_rls_ao_col_main TO copy_rls_ao_col_testuser; -GRANT ALL ON copy_rls_ao_col_lookup TO copy_rls_ao_col_testuser; - -SELECT count(*) FROM copy_rls_ao_col_main; - --- Baseline: verify RLS filters correctly (should return 400 rows: categories 1 and 2 only) -2: SET ROLE copy_rls_ao_col_testuser; COPY copy_rls_ao_col_main TO '/tmp/copy_rls_ao_col_main.csv'; - -1: BEGIN; -1: ALTER TABLE copy_rls_ao_col_lookup SET WITH (reorganize=true); - -2&: SET ROLE copy_rls_ao_col_testuser; COPY copy_rls_ao_col_main TO '/tmp/copy_rls_ao_col_main.csv'; - -1: SELECT count(*) > 0 FROM pg_stat_activity - WHERE query LIKE '%COPY copy_rls_ao_col_main%' AND wait_event_type = 'Lock'; - -1: COMMIT; -2<: - -2: RESET ROLE; - -RESET ROLE; -CREATE TABLE copy_rls_ao_col_verify (a INT, category INT) USING ao_column DISTRIBUTED BY (a); -COPY copy_rls_ao_col_verify FROM '/tmp/copy_rls_ao_col_main.csv'; -SELECT count(*) FROM copy_rls_ao_col_verify; - -DROP TABLE copy_rls_ao_col_verify; -DROP POLICY p_rls_ao_col ON copy_rls_ao_col_main; -DROP TABLE copy_rls_ao_col_main; -DROP TABLE copy_rls_ao_col_lookup; -DROP ROLE copy_rls_ao_col_testuser; - --- ============================================================ --- Test 2.5a: AO row — CTAS + concurrent reorganize --- Fixed as a side effect via BeginCopy() snapshot refresh. --- ============================================================ - -CREATE TABLE ctas_reorg_ao_row_src (a INT, b INT) USING ao_row DISTRIBUTED BY (a); -INSERT INTO ctas_reorg_ao_row_src SELECT i, i FROM generate_series(1, 1000) i; - -SELECT count(*) FROM ctas_reorg_ao_row_src; - -1: BEGIN; -1: ALTER TABLE ctas_reorg_ao_row_src SET WITH (reorganize=true); - -2&: CREATE TABLE ctas_reorg_ao_row_dst AS SELECT * FROM ctas_reorg_ao_row_src DISTRIBUTED BY (a); - -1: SELECT count(*) > 0 FROM pg_stat_activity - WHERE query LIKE 'CREATE TABLE ctas_reorg_ao_row_dst%' AND wait_event_type = 'Lock'; - -1: COMMIT; -2<: - -SELECT count(*) FROM ctas_reorg_ao_row_dst; - -DROP TABLE ctas_reorg_ao_row_dst; -DROP TABLE ctas_reorg_ao_row_src; - --- ============================================================ --- Test 2.5b: AO column — CTAS + concurrent reorganize --- Fixed as a side effect via BeginCopy() snapshot refresh. --- ============================================================ - -CREATE TABLE ctas_reorg_ao_col_src (a INT, b INT) USING ao_column DISTRIBUTED BY (a); -INSERT INTO ctas_reorg_ao_col_src SELECT i, i FROM generate_series(1, 1000) i; - -SELECT count(*) FROM ctas_reorg_ao_col_src; - -1: BEGIN; -1: ALTER TABLE ctas_reorg_ao_col_src SET WITH (reorganize=true); - -2&: CREATE TABLE ctas_reorg_ao_col_dst AS SELECT * FROM ctas_reorg_ao_col_src DISTRIBUTED BY (a); - -1: SELECT count(*) > 0 FROM pg_stat_activity - WHERE query LIKE 'CREATE TABLE ctas_reorg_ao_col_dst%' AND wait_event_type = 'Lock'; - -1: COMMIT; -2<: - -SELECT count(*) FROM ctas_reorg_ao_col_dst; - -DROP TABLE ctas_reorg_ao_col_dst; -DROP TABLE ctas_reorg_ao_col_src; - --- NOTE: Tests 2.6a/2.6b (AO variants of change distribution key + query-based COPY TO) --- removed for the same reason as test 2.6 (server crash, pre-existing bug). From 3f4ce85ae6cffc0f3f37b9134a9eb93d713d6aa4 Mon Sep 17 00:00:00 2001 From: Jianghua Yang Date: Mon, 23 Mar 2026 22:54:06 +0800 Subject: [PATCH 2/6] ORCA: support column-level COLLATE "C" collation propagation Propagate column-level C collation through the full ORCA pipeline so that sort, comparison, and aggregate operations produce correct results matching the PostgreSQL planner. Key changes: 1. Query->DXL (CTranslatorUtils::GetTableDescr): pass md_col->Collation() when creating CDXLColDescr for table columns - this was the root cause of collation being lost at the very start of the pipeline. 2. Expr->DXL (CTranslatorExprToDXL::MakeDXLTableDescr, PdxlnCTAS): pass CColumnDescriptor collation to CDXLColDescr. 3. DXL->PlStmt (CMappingColIdVarPlStmt::VarFromDXLNodeScId): when the CDXLScalarIdent has no explicit collation (e.g., partial aggregate output columns), fall back to the child TargetEntry expression's collation. This fixes Finalize Aggregate inheriting the correct collation from Partial Aggregate. 4. Aggregate collation (CTranslatorDXLToScalar): set aggcollid from inputcollid instead of TypeCollation, so min/max/string_agg use the correct column collation. 5. Expression-level COLLATE "C" fallback (walkers.c): detect when RelabelType overrides collation (from fold_constants converting CollateExpr to RelabelType) and trigger fallback to PostgreSQL planner, since ORCA does not yet handle expression-level COLLATE. The existing collation infrastructure (CMDColumn, CColumnDescriptor, CColRef, CDXLColRef, CDXLScalarIdent) was already in place but the entry point in CTranslatorUtils was not passing collation, causing all downstream stages to see collation=0. fix regress --- src/backend/gpopt/gpdbwrappers.cpp | 32 +- .../translate/CMappingColIdVarPlStmt.cpp | 34 +- .../gpopt/translate/CQueryMutators.cpp | 13 +- .../translate/CTranslatorDXLToPlStmt.cpp | 105 +- .../translate/CTranslatorDXLToScalar.cpp | 170 ++- .../translate/CTranslatorRelcacheToDXL.cpp | 3 +- .../translate/CTranslatorScalarToDXL.cpp | 8 +- .../gpopt/translate/CTranslatorUtils.cpp | 10 +- .../libgpopt/include/gpopt/base/CColRef.h | 12 +- .../gpopt/metadata/CColumnDescriptor.h | 13 +- .../gporca/libgpopt/src/base/CColRef.cpp | 3 +- .../gporca/libgpopt/src/base/CColRefTable.cpp | 3 +- .../src/metadata/CColumnDescriptor.cpp | 6 +- .../src/translate/CTranslatorDXLToExpr.cpp | 29 +- .../src/translate/CTranslatorExprToDXL.cpp | 37 +- .../translate/CTranslatorExprToDXLUtils.cpp | 12 +- .../naucrates/dxl/operators/CDXLColDescr.h | 9 +- .../naucrates/dxl/operators/CDXLColRef.h | 8 +- .../naucrates/dxl/operators/CDXLScalarIdent.h | 2 + .../dxl/parser/CParseHandlerMetadataColumn.h | 3 + .../include/naucrates/dxl/xml/dxltokens.h | 1 + .../include/naucrates/md/CMDColumn.h | 12 +- .../include/naucrates/md/IMDColumn.h | 3 + .../gporca/libnaucrates/src/md/CMDColumn.cpp | 11 +- .../src/operators/CDXLColDescr.cpp | 23 +- .../libnaucrates/src/operators/CDXLColRef.cpp | 15 +- .../src/operators/CDXLOperatorFactory.cpp | 27 +- .../src/operators/CDXLScalarIdent.cpp | 13 + .../parser/CParseHandlerMetadataColumn.cpp | 15 +- .../gporca/libnaucrates/src/xml/dxltokens.cpp | 1 + src/backend/optimizer/util/walkers.c | 132 ++- src/include/gpopt/gpdbwrappers.h | 2 +- .../regress/expected/aggregates_optimizer.out | 16 +- .../expected/btree_index_optimizer.out | 479 +++++++++ .../regress/expected/cluster_optimizer.out | 656 ++++++++++++ src/test/regress/expected/collation_orca.out | 980 ++++++++++++++++++ .../expected/create_index_optimizer.out | 11 +- .../regress/expected/equivclass_optimizer.out | 18 +- .../regress/expected/generated_optimizer.out | 114 -- .../expected/gp_array_agg_optimizer.out | 22 +- .../regress/expected/gporca_optimizer.out | 2 - .../expected/rowsecurity_optimizer.out | 94 +- .../expected/select_views_optimizer.out | 60 +- .../regress/expected/stats_ext_optimizer.out | 4 +- .../regress/expected/subselect_optimizer.out | 199 ++-- .../regress/expected/union_gp_optimizer.out | 8 - src/test/regress/expected/union_optimizer.out | 19 +- .../expected/write_parallel_optimizer.out | 86 ++ src/test/regress/init_file | 2 + src/test/regress/parallel_schedule | 1 + src/test/regress/sql/collation_orca.sql | 473 +++++++++ 51 files changed, 3540 insertions(+), 471 deletions(-) create mode 100644 src/test/regress/expected/btree_index_optimizer.out create mode 100644 src/test/regress/expected/cluster_optimizer.out create mode 100644 src/test/regress/expected/collation_orca.out create mode 100644 src/test/regress/expected/write_parallel_optimizer.out create mode 100644 src/test/regress/sql/collation_orca.sql diff --git a/src/backend/gpopt/gpdbwrappers.cpp b/src/backend/gpopt/gpdbwrappers.cpp index aca95b2cc0a..005db9e0eb3 100644 --- a/src/backend/gpopt/gpdbwrappers.cpp +++ b/src/backend/gpopt/gpdbwrappers.cpp @@ -189,17 +189,35 @@ gpdb::ExprCollation(Node *expr) { if (expr && IsA(expr, List)) { - // GPDB_91_MERGE_FIXME: collation + /* + * Resolve common collation for a list of expressions, + * matching PostgreSQL's merge_collation_state() rule: + * non-default implicit collation always beats default. + */ List *exprlist = (List *) expr; ListCell *lc; Oid collation = InvalidOid; foreach (lc, exprlist) { - Node *expr = (Node *) lfirst(lc); - if ((collation = exprCollation(expr)) != InvalidOid) + Node *child = (Node *) lfirst(lc); + Oid child_coll = exprCollation(child); + if (!OidIsValid(child_coll)) + continue; + if (!OidIsValid(collation)) { - break; + collation = child_coll; + } + else if (child_coll != collation) + { + /* + * Non-default beats default, matching PG's rule. + * If both are non-default and differ, keep the first + * (the parser would have detected a true conflict + * before ORCA sees the query). + */ + if (collation == DEFAULT_COLLATION_OID) + collation = child_coll; } } return collation; @@ -1451,12 +1469,12 @@ gpdb::MakeTargetEntry(Expr *expr, AttrNumber resno, char *resname, bool resjunk) Var * gpdb::MakeVar(Index varno, AttrNumber varattno, Oid vartype, int32 vartypmod, - Index varlevelsup) + Oid varcollid, Index varlevelsup) { GP_WRAP_START; { - // GPDB_91_MERGE_FIXME: collation - Oid collation = TypeCollation(vartype); + Oid collation = + OidIsValid(varcollid) ? varcollid : TypeCollation(vartype); return makeVar(varno, varattno, vartype, vartypmod, collation, varlevelsup); } diff --git a/src/backend/gpopt/translate/CMappingColIdVarPlStmt.cpp b/src/backend/gpopt/translate/CMappingColIdVarPlStmt.cpp index 408c1a9f68c..90a65af6525 100644 --- a/src/backend/gpopt/translate/CMappingColIdVarPlStmt.cpp +++ b/src/backend/gpopt/translate/CMappingColIdVarPlStmt.cpp @@ -109,7 +109,19 @@ CMappingColIdVarPlStmt::ParamFromDXLNodeScId(const CDXLScalarIdent *dxlop) param->paramid = elem->ParamId(); param->paramtype = CMDIdGPDB::CastMdid(elem->MdidType())->Oid(); param->paramtypmod = elem->TypeModifier(); - param->paramcollid = gpdb::TypeCollation(param->paramtype); + // Derive paramcollid from CDXLScalarIdent's collation when + // available (e.g., SubPlan output referencing a C-collation + // aggregate), falling back to type-level collation. + if (nullptr != dxlop->MdidCollation() && + dxlop->MdidCollation()->IsValid()) + { + param->paramcollid = + CMDIdGPDB::CastMdid(dxlop->MdidCollation())->Oid(); + } + else + { + param->paramcollid = gpdb::TypeCollation(param->paramtype); + } } return param; @@ -132,6 +144,8 @@ CMappingColIdVarPlStmt::VarFromDXLNodeScId(const CDXLScalarIdent *dxlop) Index varno_old = 0; AttrNumber attno_old = 0; + const TargetEntry *target_entry = nullptr; + const ULONG colid = dxlop->GetDXLColRef()->Id(); if (nullptr != m_base_table_context) { @@ -154,7 +168,7 @@ CMappingColIdVarPlStmt::VarFromDXLNodeScId(const CDXLScalarIdent *dxlop) GPOS_ASSERT(nullptr != left_context); // lookup column in the left child translation context - const TargetEntry *target_entry = left_context->GetTargetEntry(colid); + target_entry = left_context->GetTargetEntry(colid); if (nullptr != target_entry) { @@ -220,9 +234,25 @@ CMappingColIdVarPlStmt::VarFromDXLNodeScId(const CDXLScalarIdent *dxlop) } } + Oid varcollid = InvalidOid; + if (nullptr != dxlop->MdidCollation() && + dxlop->MdidCollation()->IsValid()) + { + varcollid = CMDIdGPDB::CastMdid(dxlop->MdidCollation())->Oid(); + } + else if (nullptr != target_entry) + { + // CDXLScalarIdent has no explicit collation (e.g., computed columns + // like partial aggregate results). Fall back to the child plan's + // TargetEntry expression collation so that Finalize Aggregates + // inherit the correct collation from the Partial Aggregate. + varcollid = gpdb::ExprCollation((Node *) target_entry->expr); + } + Var *var = gpdb::MakeVar(varno, attno, CMDIdGPDB::CastMdid(dxlop->MdidType())->Oid(), dxlop->TypeModifier(), + varcollid, 0 // varlevelsup ); diff --git a/src/backend/gpopt/translate/CQueryMutators.cpp b/src/backend/gpopt/translate/CQueryMutators.cpp index dde972379af..96fa61f1311 100644 --- a/src/backend/gpopt/translate/CQueryMutators.cpp +++ b/src/backend/gpopt/translate/CQueryMutators.cpp @@ -543,7 +543,7 @@ CQueryMutators::FixGroupingCols(Node *node, TargetEntry *orginal_target_entry, Var *new_var = gpdb::MakeVar( 1, // varno (AttrNumber) arity, gpdb::ExprType((Node *) orginal_target_entry->expr), - gpdb::ExprTypeMod((Node *) orginal_target_entry->expr), + gpdb::ExprTypeMod((Node *) orginal_target_entry->expr), InvalidOid, 0 // query levelsup ); @@ -897,7 +897,7 @@ CQueryMutators::MakeVarInDerivedTable(Node *node, // to the original tlist. Var *new_var = gpdb::MakeVar(1 /* varno */, attno, gpdb::ExprType((Node *) node), - gpdb::ExprTypeMod((Node *) node), + gpdb::ExprTypeMod((Node *) node), InvalidOid, context->m_current_query_level /* varlevelsup */); return new_var; @@ -925,6 +925,7 @@ CQueryMutators::FindNodeInGroupByTargetList(Node *node, gpdb::MakeVar(1 /* varno */, found_tle->resno, gpdb::ExprType((Node *) found_tle->expr), gpdb::ExprTypeMod((Node *) found_tle->expr), + InvalidOid, context->m_current_query_level /* varlevelsup */); found_tle->resjunk = false; @@ -1141,7 +1142,7 @@ CQueryMutators::MakeTopLevelTargetEntry(TargetEntry *old_target_entry, { Var *new_var = gpdb::MakeVar( 1, (AttrNumber) attno, gpdb::ExprType((Node *) old_target_entry->expr), - gpdb::ExprTypeMod((Node *) old_target_entry->expr), + gpdb::ExprTypeMod((Node *) old_target_entry->expr), InvalidOid, 0 // query levelsup ); @@ -1339,7 +1340,7 @@ CQueryMutators::EliminateDistinctClause(const Query *query) gpdb::MakeVar(1, target_entry->resno, gpdb::ExprType((Node *) target_entry->expr), gpdb::ExprTypeMod((Node *) target_entry->expr), - 0 // query levels up + InvalidOid, 0 // query levels up ); TargetEntry *new_target_entry = gpdb::MakeTargetEntry((Expr *) new_var, (AttrNumber) resno, @@ -1521,7 +1522,7 @@ CQueryMutators::NormalizeWindowProjList(CMemoryPool *mp, 1, lower_target_entry->resno, gpdb::ExprType((Node *) target_entry->expr), gpdb::ExprTypeMod((Node *) target_entry->expr), - 0 // query levels up + InvalidOid, 0 // query levels up ); TargetEntry *upper_target_entry = gpdb::MakeTargetEntry( (Expr *) new_var, ulResNoNew, target_entry->resname, @@ -1624,7 +1625,7 @@ CQueryMutators::RunWindowProjListMutator(Node *node, Var *new_var = gpdb::MakeVar( 1, // derived query which is now the only table in FROM expression (AttrNumber) resno, gpdb::ExprType(node), gpdb::ExprTypeMod(node), - 0 // query levelsup + InvalidOid, 0 // query levelsup ); return (Node *) new_var; diff --git a/src/backend/gpopt/translate/CTranslatorDXLToPlStmt.cpp b/src/backend/gpopt/translate/CTranslatorDXLToPlStmt.cpp index 4acf13bd606..6d4417468ab 100644 --- a/src/backend/gpopt/translate/CTranslatorDXLToPlStmt.cpp +++ b/src/backend/gpopt/translate/CTranslatorDXLToPlStmt.cpp @@ -926,7 +926,7 @@ TranslateDXLIndexTList(const IMDRelation *md_rel, const IMDIndex *md_index, Expr *indexvar = (Expr *) gpdb::MakeVar( new_varno, col->AttrNum(), CMDIdGPDB::CastMdid(col->MdidType())->Oid(), - col->TypeModifier() /*vartypmod*/, 0 /*varlevelsup*/); + col->TypeModifier() /*vartypmod*/, InvalidOid, 0 /*varlevelsup*/); target_entry->expr = indexvar; // Fix up proj list. Since index only scan does not read full tuples, @@ -962,7 +962,7 @@ TranslateDXLIndexTList(const IMDRelation *md_rel, const IMDIndex *md_index, Expr *indexvar = (Expr *) gpdb::MakeVar( new_varno, col->AttrNum(), CMDIdGPDB::CastMdid(col->MdidType())->Oid(), - col->TypeModifier() /*vartypmod*/, 0 /*varlevelsup*/); + col->TypeModifier() /*vartypmod*/, InvalidOid, 0 /*varlevelsup*/); target_entry->expr = indexvar; for (ULONG j = 0; j < table_descr->Arity(); j++) @@ -1691,11 +1691,16 @@ CTranslatorDXLToPlStmt::TranslateDXLTvf( ListCell *lc_target_entry = nullptr; + // Save existing funccolcollations (set by TranslateDXLTvfToRangeTblEntry + // with correct collation from the function expression/DXL). + List *saved_collations = rtfunc->funccolcollations; + rtfunc->funccolnames = NIL; rtfunc->funccoltypes = NIL; rtfunc->funccoltypmods = NIL; rtfunc->funccolcollations = NIL; rtfunc->funccolcount = gpdb::ListLength(target_list); + ListCell *lc_saved = (saved_collations != NIL) ? list_head(saved_collations) : nullptr; ForEach(lc_target_entry, target_list) { TargetEntry *target_entry = (TargetEntry *) lfirst(lc_target_entry); @@ -1703,16 +1708,40 @@ CTranslatorDXLToPlStmt::TranslateDXLTvf( GPOS_ASSERT(InvalidOid != oid_type); INT typ_mod = gpdb::ExprTypeMod((Node *) target_entry->expr); - Oid collation_type_oid = gpdb::TypeCollation(oid_type); + Oid expr_coll = gpdb::ExprCollation((Node *) target_entry->expr); + + // Prefer the pre-computed collation from the RTE (derived from the + // function call expression) over the target entry Var collation, + // because the Var may lack collation from the DXL representation. + Oid saved_coll = (lc_saved != nullptr) ? lfirst_oid(lc_saved) : InvalidOid; + + // For polymorphic SRFs (e.g., unnest), ORCA generates an empty + // project list so saved_coll is unavailable. The target entry Var + // is created by ProcessRecordFuncTargetList from the return type, + // which only carries DEFAULT collation. Fall back to the FuncExpr's + // collation (funccollid), which correctly reflects the input's + // collation through our expression-level collation propagation fix. + Oid func_coll = gpdb::ExprCollation((Node *) rtfunc->funcexpr); + Oid collation_type_oid; + if (OidIsValid(saved_coll) && saved_coll != DEFAULT_COLLATION_OID) + collation_type_oid = saved_coll; + else if (OidIsValid(func_coll) && func_coll != DEFAULT_COLLATION_OID) + collation_type_oid = func_coll; + else if (OidIsValid(expr_coll)) + collation_type_oid = expr_coll; + else + collation_type_oid = gpdb::TypeCollation(oid_type); rtfunc->funccolnames = gpdb::LAppend( rtfunc->funccolnames, gpdb::MakeStringValue(target_entry->resname)); rtfunc->funccoltypes = gpdb::LAppendOid(rtfunc->funccoltypes, oid_type); rtfunc->funccoltypmods = gpdb::LAppendInt(rtfunc->funccoltypmods, typ_mod); - // GPDB_91_MERGE_FIXME: collation rtfunc->funccolcollations = gpdb::LAppendOid(rtfunc->funccolcollations, collation_type_oid); + + if (lc_saved != nullptr) + lc_saved = lnext(saved_collations, lc_saved); } func_scan->functions = ListMake1(rtfunc); @@ -1826,9 +1855,14 @@ CTranslatorDXLToPlStmt::TranslateDXLTvfToRangeTblEntry( func_expr->args = gpdb::LAppend(func_expr->args, pexprFuncArg); } - // GPDB_91_MERGE_FIXME: collation func_expr->inputcollid = gpdb::ExprCollation((Node *) func_expr->args); - func_expr->funccollid = gpdb::TypeCollation(func_expr->funcresulttype); + { + Oid rtc = gpdb::TypeCollation(func_expr->funcresulttype); + func_expr->funccollid = + OidIsValid(rtc) + ? (OidIsValid(func_expr->inputcollid) ? func_expr->inputcollid : rtc) + : InvalidOid; + } // Populate RangeTblFunction::funcparams, by walking down the entire // func_expr to capture ids of all the PARAMs @@ -1846,8 +1880,44 @@ CTranslatorDXLToPlStmt::TranslateDXLTvfToRangeTblEntry( rtfunc->funccolcount = (int) num_of_cols; rtfunc->funcparams = funcparams; - // GPDB_91_MERGE_FIXME: collation - // set rtfunc->funccoltypemods & rtfunc->funccolcollations? + + // Derive column collations and type modifiers from the project list + { + rtfunc->funccoltypmods = NIL; + rtfunc->funccolcollations = NIL; + rtfunc->funccoltypes = NIL; + for (ULONG ul = 0; ul < num_of_cols; ul++) + { + CDXLNode *proj_elem_dxlnode = (*project_list_dxlnode)[ul]; + CDXLNode *expr_dxlnode = (*proj_elem_dxlnode)[0]; + CDXLScalarIdent *sc_ident = + CDXLScalarIdent::Cast(expr_dxlnode->GetOperator()); + + Oid col_type = CMDIdGPDB::CastMdid(sc_ident->MdidType())->Oid(); + Oid col_coll = InvalidOid; + if (nullptr != sc_ident->MdidCollation() && + sc_ident->MdidCollation()->IsValid()) + { + col_coll = CMDIdGPDB::CastMdid(sc_ident->MdidCollation())->Oid(); + } + if (!OidIsValid(col_coll)) + { + // Fall back: derive from function expression collation + col_coll = gpdb::ExprCollation((Node *) rtfunc->funcexpr); + } + if (!OidIsValid(col_coll)) + { + col_coll = gpdb::TypeCollation(col_type); + } + + rtfunc->funccoltypes = + gpdb::LAppendOid(rtfunc->funccoltypes, col_type); + rtfunc->funccoltypmods = + gpdb::LAppendInt(rtfunc->funccoltypmods, sc_ident->TypeModifier()); + rtfunc->funccolcollations = + gpdb::LAppendOid(rtfunc->funccolcollations, col_coll); + } + } rte->functions = ListMake1(rtfunc); rte->inFromCl = true; @@ -2752,7 +2822,8 @@ CTranslatorDXLToPlStmt::TranslateDXLRedistributeMotionToResultHashFilters( TargetEntry *te = (TargetEntry *) lfirst(lc); Var *var = gpdb::MakeVar( OUTER_VAR, te->resno, gpdb::ExprType((Node *) te->expr), - gpdb::ExprTypeMod((Node *) te->expr), 0 /* varlevelsup */); + gpdb::ExprTypeMod((Node *) te->expr), InvalidOid, + 0 /* varlevelsup */); TargetEntry *new_te = gpdb::MakeTargetEntry((Expr *) var, ul, /* resno */ te->resname, te->resjunk); @@ -4332,6 +4403,10 @@ CTranslatorDXLToPlStmt::TranslateDXLAppend( idxVarno, attno, CMDIdGPDB::CastMdid(sc_ident_dxlop->MdidType())->Oid(), sc_ident_dxlop->TypeModifier(), + (nullptr != sc_ident_dxlop->MdidCollation() && + sc_ident_dxlop->MdidCollation()->IsValid()) + ? CMDIdGPDB::CastMdid(sc_ident_dxlop->MdidCollation())->Oid() + : InvalidOid, 0 // varlevelsup ); @@ -4560,7 +4635,8 @@ CTranslatorDXLToPlStmt::TranslateDXLCTEConsumerToSharedScan( Var *var = gpdb::MakeVar(OUTER_VAR, varattno, oid_type, - sc_ident_dxlop->TypeModifier(), 0 /* varlevelsup */); + sc_ident_dxlop->TypeModifier(), InvalidOid, + 0 /* varlevelsup */); CHAR *resname = CTranslatorUtils::CreateMultiByteCharStringFromWCString( sc_proj_elem_dxlop->GetMdNameAlias()->GetMDName()->GetBuffer()); @@ -6048,7 +6124,7 @@ CTranslatorDXLToPlStmt::TranslateDXLProjectListToHashTargetList( // create a Var expression for this target list entry expression Var *var = gpdb::MakeVar(OUTER_VAR, te_child->resno, oid_type, type_modifier, - 0 // varlevelsup + InvalidOid, 0 // varlevelsup ); // set old varno and varattno since makeVar does not set them @@ -6549,7 +6625,7 @@ CTranslatorDXLToPlStmt::AddJunkTargetEntryForColId( INT type_modifier = gpdb::ExprTypeMod((Node *) target_entry->expr); Var *var = gpdb::MakeVar(OUTER_VAR, target_entry->resno, expr_oid, type_modifier, - 0 // varlevelsup + InvalidOid, 0 // varlevelsup ); ULONG resno = gpdb::ListLength(*target_list) + 1; CHAR *resname_str = PStrDup(resname); @@ -7170,7 +7246,8 @@ CTranslatorDXLToPlStmt::TranslateNestLoopParamList( Var *new_var = gpdb::MakeVar(OUTER_VAR, target_entry->resno, old_var->vartype, - old_var->vartypmod, 0 /*varlevelsup*/); + old_var->vartypmod, old_var->varcollid, + 0 /*varlevelsup*/); new_var->varnosyn = old_var->varnosyn; new_var->varattnosyn = old_var->varattnosyn; @@ -7219,7 +7296,7 @@ CTranslatorDXLToPlStmt::CreateDirectCopyTargetList(List *target_list) Node *expr = (Node *) te->expr; Var *var = gpdb::MakeVar(OUTER_VAR, te->resno, gpdb::ExprType(expr), - gpdb::ExprTypeMod(expr), 0 /* varlevelsup */); + gpdb::ExprTypeMod(expr), InvalidOid, 0 /* varlevelsup */); TargetEntry *new_te = gpdb::MakeTargetEntry((Expr *) var, te->resno, te->resname, te->resjunk); result_target_list = gpdb::LAppend(result_target_list, new_te); diff --git a/src/backend/gpopt/translate/CTranslatorDXLToScalar.cpp b/src/backend/gpopt/translate/CTranslatorDXLToScalar.cpp index 5ab66c9e76e..12bb6b5409e 100644 --- a/src/backend/gpopt/translate/CTranslatorDXLToScalar.cpp +++ b/src/backend/gpopt/translate/CTranslatorDXLToScalar.cpp @@ -259,8 +259,7 @@ CTranslatorDXLToScalar::TranslateDXLScalarIfStmtToScalar( CaseExpr *case_expr = MakeNode(CaseExpr); case_expr->casetype = CMDIdGPDB::CastMdid(scalar_if_stmt_dxl->GetResultTypeMdId())->Oid(); - // GPDB_91_MERGE_FIXME: collation - case_expr->casecollid = gpdb::TypeCollation(case_expr->casetype); + // casecollid is set after translating children (see below) CDXLNode *curr_node = const_cast(scalar_if_stmt_node); Expr *else_expr = nullptr; @@ -299,6 +298,35 @@ CTranslatorDXLToScalar::TranslateDXLScalarIfStmtToScalar( case_expr->defresult = else_expr; + // Derive output collation from children: prefer non-default collation + // (matches PostgreSQL's collation conflict resolution where explicit/column + // collation wins over implicit default from constants). + { + Oid strong_coll = InvalidOid; + Oid any_coll = InvalidOid; + ListCell *lc; + foreach (lc, case_expr->args) + { + CaseWhen *cw = (CaseWhen *) lfirst(lc); + Oid c = gpdb::ExprCollation((Node *) cw->result); + if (OidIsValid(c) && c != DEFAULT_COLLATION_OID && !OidIsValid(strong_coll)) + strong_coll = c; + if (OidIsValid(c) && !OidIsValid(any_coll)) + any_coll = c; + } + if (case_expr->defresult) + { + Oid c = gpdb::ExprCollation((Node *) case_expr->defresult); + if (OidIsValid(c) && c != DEFAULT_COLLATION_OID && !OidIsValid(strong_coll)) + strong_coll = c; + if (OidIsValid(c) && !OidIsValid(any_coll)) + any_coll = c; + } + Oid resolved = OidIsValid(strong_coll) ? strong_coll : any_coll; + case_expr->casecollid = + OidIsValid(resolved) ? resolved : gpdb::TypeCollation(case_expr->casetype); + } + return (Expr *) case_expr; } @@ -319,8 +347,7 @@ CTranslatorDXLToScalar::TranslateDXLScalarSwitchToScalar( CaseExpr *case_expr = MakeNode(CaseExpr); case_expr->casetype = CMDIdGPDB::CastMdid(dxlop->MdidType())->Oid(); - // GPDB_91_MERGE_FIXME: collation - case_expr->casecollid = gpdb::TypeCollation(case_expr->casetype); + // casecollid is set after translating children (see below) // translate arg child case_expr->arg = TranslateDXLToScalar((*scalar_switch_node)[0], colid_var); @@ -350,6 +377,33 @@ CTranslatorDXLToScalar::TranslateDXLScalarSwitchToScalar( } } + // Derive output collation from children: prefer non-default collation + { + Oid strong_coll = InvalidOid; + Oid any_coll = InvalidOid; + ListCell *lc; + foreach (lc, case_expr->args) + { + CaseWhen *cw = (CaseWhen *) lfirst(lc); + Oid c = gpdb::ExprCollation((Node *) cw->result); + if (OidIsValid(c) && c != DEFAULT_COLLATION_OID && !OidIsValid(strong_coll)) + strong_coll = c; + if (OidIsValid(c) && !OidIsValid(any_coll)) + any_coll = c; + } + if (case_expr->defresult) + { + Oid c = gpdb::ExprCollation((Node *) case_expr->defresult); + if (OidIsValid(c) && c != DEFAULT_COLLATION_OID && !OidIsValid(strong_coll)) + strong_coll = c; + if (OidIsValid(c) && !OidIsValid(any_coll)) + any_coll = c; + } + Oid resolved = OidIsValid(strong_coll) ? strong_coll : any_coll; + case_expr->casecollid = + OidIsValid(resolved) ? resolved : gpdb::TypeCollation(case_expr->casetype); + } + return (Expr *) case_expr; } @@ -423,9 +477,13 @@ CTranslatorDXLToScalar::TranslateDXLScalarOpExprToScalar( op_expr->args = TranslateScalarChildren(op_expr->args, scalar_op_expr_node, colid_var); - // GPDB_91_MERGE_FIXME: collation op_expr->inputcollid = gpdb::ExprCollation((Node *) op_expr->args); - op_expr->opcollid = gpdb::TypeCollation(op_expr->opresulttype); + Oid result_type_coll = gpdb::TypeCollation(op_expr->opresulttype); + op_expr->opcollid = + OidIsValid(result_type_coll) + ? (OidIsValid(op_expr->inputcollid) ? op_expr->inputcollid + : result_type_coll) + : InvalidOid; return (Expr *) op_expr; } @@ -552,9 +610,14 @@ CTranslatorDXLToScalar::TranslateDXLScalarDistinctToScalar( Expr *right_expr = TranslateDXLToScalar(right_node, colid_var); dist_expr->args = ListMake2(left_expr, right_expr); - // GPDB_91_MERGE_FIXME: collation - dist_expr->opcollid = gpdb::TypeCollation(dist_expr->opresulttype); dist_expr->inputcollid = gpdb::ExprCollation((Node *) dist_expr->args); + { + Oid rtc = gpdb::TypeCollation(dist_expr->opresulttype); + dist_expr->opcollid = + OidIsValid(rtc) + ? (OidIsValid(dist_expr->inputcollid) ? dist_expr->inputcollid : rtc) + : InvalidOid; + } return (Expr *) dist_expr; } @@ -726,9 +789,13 @@ CTranslatorDXLToScalar::TranslateDXLScalarAggrefToScalar( aggref->aggfnoid, aggtranstype, inputTypes, numArguments); aggref->aggtranstype = aggtranstype; - // GPDB_91_MERGE_FIXME: collation + // Set aggregate collation from input arguments when available, + // so that min/max etc. use the correct column-level collation. aggref->inputcollid = gpdb::ExprCollation((Node *) args); - aggref->aggcollid = gpdb::TypeCollation(aggref->aggtype); + aggref->aggcollid = + OidIsValid(aggref->inputcollid) + ? aggref->inputcollid + : gpdb::TypeCollation(aggref->aggtype); return (Expr *) aggref; } @@ -768,9 +835,14 @@ CTranslatorDXLToScalar::TranslateDXLScalarWindowRefToScalar( // translate the arguments of the window function window_func->args = TranslateScalarChildren(window_func->args, scalar_winref_node, colid_var); - // GPDB_91_MERGE_FIXME: collation - window_func->wincollid = gpdb::TypeCollation(window_func->wintype); window_func->inputcollid = gpdb::ExprCollation((Node *) window_func->args); + { + Oid rtc = gpdb::TypeCollation(window_func->wintype); + window_func->wincollid = + OidIsValid(rtc) + ? (OidIsValid(window_func->inputcollid) ? window_func->inputcollid : rtc) + : InvalidOid; + } return (Expr *) window_func; } @@ -801,9 +873,14 @@ CTranslatorDXLToScalar::TranslateDXLScalarFuncExprToScalar( scalar_func_expr_node, colid_var); func_expr->funcvariadic = dxlop->IsFuncVariadic(); - // GPDB_91_MERGE_FIXME: collation func_expr->inputcollid = gpdb::ExprCollation((Node *) func_expr->args); - func_expr->funccollid = gpdb::TypeCollation(func_expr->funcresulttype); + { + Oid rtc = gpdb::TypeCollation(func_expr->funcresulttype); + func_expr->funccollid = + OidIsValid(rtc) + ? (OidIsValid(func_expr->inputcollid) ? func_expr->inputcollid : rtc) + : InvalidOid; + } return (Expr *) func_expr; } @@ -1167,10 +1244,15 @@ CTranslatorDXLToScalar::TranslateSubplanFromChildPlan( gpdb::ListLength(dxl_to_plstmt_ctxt->GetSubplanEntriesList()); subplan->plan_name = GetSubplanAlias(subplan->plan_id); subplan->is_initplan = false; - subplan->firstColType = gpdb::ExprType( - (Node *) ((TargetEntry *) gpdb::ListNth(plan->targetlist, 0))->expr); - // GPDB_91_MERGE_FIXME: collation - subplan->firstColCollation = gpdb::TypeCollation(subplan->firstColType); + { + Expr *first_expr = + ((TargetEntry *) gpdb::ListNth(plan->targetlist, 0))->expr; + subplan->firstColType = gpdb::ExprType((Node *) first_expr); + Oid expr_coll = gpdb::ExprCollation((Node *) first_expr); + subplan->firstColCollation = + OidIsValid(expr_coll) ? expr_coll + : gpdb::TypeCollation(subplan->firstColType); + } subplan->firstColTypmod = -1; subplan->subLinkType = slink; subplan->is_multirow = false; @@ -1400,11 +1482,17 @@ CTranslatorDXLToScalar::TranslateDXLScalarNullIfToScalar( GPOS_ASSERT(2 == scalar_null_if_node->Arity()); scalar_null_if_expr->args = TranslateScalarChildren( scalar_null_if_expr->args, scalar_null_if_node, colid_var); - // GPDB_91_MERGE_FIXME: collation - scalar_null_if_expr->opcollid = - gpdb::TypeCollation(scalar_null_if_expr->opresulttype); scalar_null_if_expr->inputcollid = gpdb::ExprCollation((Node *) scalar_null_if_expr->args); + { + Oid rtc = gpdb::TypeCollation(scalar_null_if_expr->opresulttype); + scalar_null_if_expr->opcollid = + OidIsValid(rtc) + ? (OidIsValid(scalar_null_if_expr->inputcollid) + ? scalar_null_if_expr->inputcollid + : rtc) + : InvalidOid; + } return (Expr *) scalar_null_if_expr; } @@ -1430,9 +1518,14 @@ CTranslatorDXLToScalar::TranslateDXLScalarCastWithChildExpr( func_expr->args = NIL; func_expr->args = gpdb::LAppend(func_expr->args, child_expr); - // GPDB_91_MERGE_FIXME: collation func_expr->inputcollid = gpdb::ExprCollation((Node *) func_expr->args); - func_expr->funccollid = gpdb::TypeCollation(func_expr->funcresulttype); + { + Oid rtc = gpdb::TypeCollation(func_expr->funcresulttype); + func_expr->funccollid = + OidIsValid(rtc) + ? (OidIsValid(func_expr->inputcollid) ? func_expr->inputcollid : rtc) + : InvalidOid; + } return (Expr *) func_expr; } @@ -1598,12 +1691,20 @@ CTranslatorDXLToScalar::TranslateDXLScalarCoalesceToScalar( CoalesceExpr *coalesce = MakeNode(CoalesceExpr); coalesce->coalescetype = CMDIdGPDB::CastMdid(dxlop->MdidType())->Oid(); - // GPDB_91_MERGE_FIXME: collation - coalesce->coalescecollid = gpdb::TypeCollation(coalesce->coalescetype); coalesce->args = TranslateScalarChildren(coalesce->args, scalar_coalesce_node, colid_var); coalesce->location = -1; + // Derive output collation from children + { + Oid argcoll = gpdb::ExprCollation((Node *) coalesce->args); + Oid rtc = gpdb::TypeCollation(coalesce->coalescetype); + coalesce->coalescecollid = + OidIsValid(rtc) + ? (OidIsValid(argcoll) ? argcoll : rtc) + : InvalidOid; + } + return (Expr *) coalesce; } @@ -1625,12 +1726,17 @@ CTranslatorDXLToScalar::TranslateDXLScalarMinMaxToScalar( MinMaxExpr *min_max_expr = MakeNode(MinMaxExpr); min_max_expr->minmaxtype = CMDIdGPDB::CastMdid(dxlop->MdidType())->Oid(); - min_max_expr->minmaxcollid = gpdb::TypeCollation(min_max_expr->minmaxtype); min_max_expr->args = TranslateScalarChildren( min_max_expr->args, scalar_min_max_node, colid_var); - // GPDB_91_MERGE_FIXME: collation min_max_expr->inputcollid = gpdb::ExprCollation((Node *) min_max_expr->args); + { + Oid rtc = gpdb::TypeCollation(min_max_expr->minmaxtype); + min_max_expr->minmaxcollid = + OidIsValid(rtc) + ? (OidIsValid(min_max_expr->inputcollid) ? min_max_expr->inputcollid : rtc) + : InvalidOid; + } min_max_expr->location = -1; CDXLScalarMinMax::EdxlMinMaxType min_max_type = dxlop->GetMinMaxType(); @@ -2062,10 +2168,14 @@ CTranslatorDXLToScalar::TranslateDXLScalarCmpToScalar( op_expr->args = ListMake2(left_expr, right_expr); - // GPDB_91_MERGE_FIXME: collation op_expr->inputcollid = gpdb::ExprCollation((Node *) op_expr->args); - op_expr->opcollid = gpdb::TypeCollation(op_expr->opresulttype); - ; + { + Oid rtc = gpdb::TypeCollation(op_expr->opresulttype); + op_expr->opcollid = + OidIsValid(rtc) + ? (OidIsValid(op_expr->inputcollid) ? op_expr->inputcollid : rtc) + : InvalidOid; + } return (Expr *) op_expr; } diff --git a/src/backend/gpopt/translate/CTranslatorRelcacheToDXL.cpp b/src/backend/gpopt/translate/CTranslatorRelcacheToDXL.cpp index ee4b8888b19..160ce55a3d0 100644 --- a/src/backend/gpopt/translate/CTranslatorRelcacheToDXL.cpp +++ b/src/backend/gpopt/translate/CTranslatorRelcacheToDXL.cpp @@ -695,7 +695,8 @@ CTranslatorRelcacheToDXL::RetrieveRelColumns(CMemoryPool *mp, CMDColumn *md_col = GPOS_NEW(mp) CMDColumn(md_colname, att->attnum, mdid_col, att->atttypmod, - !att->attnotnull, att->attisdropped, col_len); + !att->attnotnull, att->attisdropped, col_len, + att->attcollation); mdcol_array->Append(md_col); } diff --git a/src/backend/gpopt/translate/CTranslatorScalarToDXL.cpp b/src/backend/gpopt/translate/CTranslatorScalarToDXL.cpp index 7256409eb54..e3be40cff89 100644 --- a/src/backend/gpopt/translate/CTranslatorScalarToDXL.cpp +++ b/src/backend/gpopt/translate/CTranslatorScalarToDXL.cpp @@ -252,9 +252,15 @@ CTranslatorScalarToDXL::TranslateVarToDXL( CMDName *mdname = GPOS_NEW(m_mp) CMDName(m_mp, str); // create a column reference for the given var + IMDId *mdid_collation = nullptr; + if (OidIsValid(var->varcollid)) + { + mdid_collation = + GPOS_NEW(m_mp) CMDIdGPDB(IMDId::EmdidGeneral, var->varcollid); + } CDXLColRef *dxl_colref = GPOS_NEW(m_mp) CDXLColRef( mdname, id, GPOS_NEW(m_mp) CMDIdGPDB(IMDId::EmdidGeneral, var->vartype), - var->vartypmod); + var->vartypmod, mdid_collation); // create the scalar ident operator CDXLScalarIdent *scalar_ident = diff --git a/src/backend/gpopt/translate/CTranslatorUtils.cpp b/src/backend/gpopt/translate/CTranslatorUtils.cpp index 0887c72725c..d77bdf87489 100644 --- a/src/backend/gpopt/translate/CTranslatorUtils.cpp +++ b/src/backend/gpopt/translate/CTranslatorUtils.cpp @@ -175,11 +175,17 @@ CTranslatorUtils::GetTableDescr(CMemoryPool *mp, CMDAccessor *md_accessor, col_type->AddRef(); // create a column descriptor for the column + IMDId *mdid_collation = nullptr; + if (0 != md_col->Collation()) + { + mdid_collation = GPOS_NEW(mp) + CMDIdGPDB(IMDId::EmdidGeneral, md_col->Collation()); + } CDXLColDescr *dxl_col_descr = GPOS_NEW(mp) CDXLColDescr(col, id_generator->next_id(), md_col->AttrNum(), col_type, md_col->TypeModifier(), /* type_modifier */ false, /* fColDropped */ - md_col->Length()); + md_col->Length(), mdid_collation); table_descr->AddColumnDescr(dxl_col_descr); } @@ -1612,7 +1618,7 @@ CTranslatorUtils::GetColId(ULONG query_level, INT varno, INT var_attno, IMDId *mdid, CMappingVarColId *var_colid_mapping) { OID oid = CMDIdGPDB::CastMdid(mdid)->Oid(); - Var *var = gpdb::MakeVar(varno, var_attno, oid, -1, 0); + Var *var = gpdb::MakeVar(varno, var_attno, oid, -1, InvalidOid, 0); ULONG colid = var_colid_mapping->GetColId(query_level, var, EpspotNone); gpdb::GPDBFree(var); diff --git a/src/backend/gporca/libgpopt/include/gpopt/base/CColRef.h b/src/backend/gporca/libgpopt/include/gpopt/base/CColRef.h index af201349334..c87b5829a94 100644 --- a/src/backend/gporca/libgpopt/include/gpopt/base/CColRef.h +++ b/src/backend/gporca/libgpopt/include/gpopt/base/CColRef.h @@ -86,6 +86,9 @@ class CColRef : public gpos::DbgPrintMixin // table info IMDId *m_mdid_table; + // collation OID (metadata for translation, does not affect column identity) + OID m_collation; + // debug function const char* UsedStatusToString(EUsedStatus status) const; @@ -102,7 +105,7 @@ class CColRef : public gpos::DbgPrintMixin // ctor CColRef(const IMDType *pmdtype, const INT type_modifier, ULONG id, - const CName *pname); + const CName *pname, OID collation = 0); // dtor virtual ~CColRef(); @@ -121,6 +124,13 @@ class CColRef : public gpos::DbgPrintMixin return m_type_modifier; } + // collation + OID + Collation() const + { + return m_collation; + } + // name const CName & Name() const diff --git a/src/backend/gporca/libgpopt/include/gpopt/metadata/CColumnDescriptor.h b/src/backend/gporca/libgpopt/include/gpopt/metadata/CColumnDescriptor.h index aa4a88a1ba1..97e62357720 100644 --- a/src/backend/gporca/libgpopt/include/gpopt/metadata/CColumnDescriptor.h +++ b/src/backend/gporca/libgpopt/include/gpopt/metadata/CColumnDescriptor.h @@ -59,11 +59,15 @@ class CColumnDescriptor : public CRefCount, // is the column a partition col BOOL m_is_part_col; + // collation OID + OID m_collation; + public: // ctor CColumnDescriptor(CMemoryPool *mp, const IMDType *pmdtype, INT type_modifier, const CName &name, INT attno, - BOOL is_nullable, ULONG ulWidth = gpos::ulong_max); + BOOL is_nullable, ULONG ulWidth = gpos::ulong_max, + OID collation = 0); // dtor ~CColumnDescriptor() override; @@ -117,6 +121,13 @@ class CColumnDescriptor : public CRefCount, return m_width; } + // collation of the column + OID + Collation() const + { + return m_collation; + } + // is this a distribution column BOOL IsDistCol() const diff --git a/src/backend/gporca/libgpopt/src/base/CColRef.cpp b/src/backend/gporca/libgpopt/src/base/CColRef.cpp index 32a923cf890..41f6ace5959 100644 --- a/src/backend/gporca/libgpopt/src/base/CColRef.cpp +++ b/src/backend/gporca/libgpopt/src/base/CColRef.cpp @@ -34,12 +34,13 @@ const ULONG CColRef::m_ulInvalid = gpos::ulong_max; // //--------------------------------------------------------------------------- CColRef::CColRef(const IMDType *pmdtype, const INT type_modifier, ULONG id, - const CName *pname) + const CName *pname, OID collation) : m_pmdtype(pmdtype), m_type_modifier(type_modifier), m_pname(pname), m_used(EUnknown), m_mdid_table(nullptr), + m_collation(collation), m_id(id) { GPOS_ASSERT(nullptr != pmdtype); diff --git a/src/backend/gporca/libgpopt/src/base/CColRefTable.cpp b/src/backend/gporca/libgpopt/src/base/CColRefTable.cpp index 244fb343908..6b241f1ad15 100644 --- a/src/backend/gporca/libgpopt/src/base/CColRefTable.cpp +++ b/src/backend/gporca/libgpopt/src/base/CColRefTable.cpp @@ -29,7 +29,8 @@ using namespace gpmd; //--------------------------------------------------------------------------- CColRefTable::CColRefTable(const CColumnDescriptor *pcoldesc, ULONG id, const CName *pname, ULONG ulOpSource) - : CColRef(pcoldesc->RetrieveType(), pcoldesc->TypeModifier(), id, pname), + : CColRef(pcoldesc->RetrieveType(), pcoldesc->TypeModifier(), id, pname, + pcoldesc->Collation()), m_iAttno(0), m_ulSourceOpId(ulOpSource), m_width(pcoldesc->Width()) diff --git a/src/backend/gporca/libgpopt/src/metadata/CColumnDescriptor.cpp b/src/backend/gporca/libgpopt/src/metadata/CColumnDescriptor.cpp index f3d1ae474b3..36cf51abcb0 100644 --- a/src/backend/gporca/libgpopt/src/metadata/CColumnDescriptor.cpp +++ b/src/backend/gporca/libgpopt/src/metadata/CColumnDescriptor.cpp @@ -30,7 +30,8 @@ FORCE_GENERATE_DBGSTR(CColumnDescriptor); //--------------------------------------------------------------------------- CColumnDescriptor::CColumnDescriptor(CMemoryPool *mp, const IMDType *pmdtype, INT type_modifier, const CName &name, - INT attno, BOOL is_nullable, ULONG ulWidth) + INT attno, BOOL is_nullable, ULONG ulWidth, + OID collation) : m_pmdtype(pmdtype), m_type_modifier(type_modifier), m_name(mp, name), @@ -38,7 +39,8 @@ CColumnDescriptor::CColumnDescriptor(CMemoryPool *mp, const IMDType *pmdtype, m_is_nullable(is_nullable), m_width(ulWidth), m_is_dist_col(false), - m_is_part_col(false) + m_is_part_col(false), + m_collation(collation) { GPOS_ASSERT(nullptr != pmdtype); diff --git a/src/backend/gporca/libgpopt/src/translate/CTranslatorDXLToExpr.cpp b/src/backend/gporca/libgpopt/src/translate/CTranslatorDXLToExpr.cpp index c592a7ab7f3..7765d77128f 100644 --- a/src/backend/gporca/libgpopt/src/translate/CTranslatorDXLToExpr.cpp +++ b/src/backend/gporca/libgpopt/src/translate/CTranslatorDXLToExpr.cpp @@ -603,11 +603,18 @@ CTranslatorDXLToExpr::PexprLogicalTVF(const CDXLNode *dxlnode) m_mp, pdxlcoldesc->MdName()->GetMDName()->GetBuffer()); INT attrnum = pdxlcoldesc->AttrNum(); + OID collation = 0; + if (nullptr != pdxlcoldesc->MdidCollation() && + pdxlcoldesc->MdidCollation()->IsValid()) + { + collation = + CMDIdGPDB::CastMdid(pdxlcoldesc->MdidCollation())->Oid(); + } CColumnDescriptor *pcoldesc = GPOS_NEW(m_mp) CColumnDescriptor(m_mp, pmdtype, pdxlcoldesc->TypeModifier(), CName(m_mp, &strColName), attrnum, true, // is_nullable - pdxlcoldesc->Width()); + pdxlcoldesc->Width(), collation); pdrgpcoldesc->Append(pcoldesc); } @@ -2377,9 +2384,17 @@ CTranslatorDXLToExpr::Ptabdesc(CDXLTableDescr *table_descr) INT attrnum = pdxlcoldesc->AttrNum(); const ULONG ulWidth = pdxlcoldesc->Width(); + OID col_collation = 0; + if (nullptr != pdxlcoldesc->MdidCollation() && + pdxlcoldesc->MdidCollation()->IsValid()) + { + col_collation = + CMDIdGPDB::CastMdid(pdxlcoldesc->MdidCollation())->Oid(); + } CColumnDescriptor *pcoldesc = GPOS_NEW(m_mp) CColumnDescriptor( m_mp, pmdtype, pdxlcoldesc->TypeModifier(), - CName(m_mp, &strColName), attrnum, is_nullable, ulWidth); + CName(m_mp, &strColName), attrnum, is_nullable, ulWidth, + col_collation); ptabdesc->AddColumn(pcoldesc); } @@ -2578,9 +2593,17 @@ CTranslatorDXLToExpr::PtabdescFromCTAS(CDXLLogicalCTAS *pdxlopCTAS) INT attrnum = pdxlcoldesc->AttrNum(); const ULONG ulWidth = pdxlcoldesc->Width(); + OID col_collation = 0; + if (nullptr != pdxlcoldesc->MdidCollation() && + pdxlcoldesc->MdidCollation()->IsValid()) + { + col_collation = + CMDIdGPDB::CastMdid(pdxlcoldesc->MdidCollation())->Oid(); + } CColumnDescriptor *pcoldesc = GPOS_NEW(m_mp) CColumnDescriptor( m_mp, pmdtype, pdxlcoldesc->TypeModifier(), - CName(m_mp, &strColName), attrnum, is_nullable, ulWidth); + CName(m_mp, &strColName), attrnum, is_nullable, ulWidth, + col_collation); ptabdesc->AddColumn(pcoldesc); } diff --git a/src/backend/gporca/libgpopt/src/translate/CTranslatorExprToDXL.cpp b/src/backend/gporca/libgpopt/src/translate/CTranslatorExprToDXL.cpp index 6119e2ba71f..ed2aec5be4a 100644 --- a/src/backend/gporca/libgpopt/src/translate/CTranslatorExprToDXL.cpp +++ b/src/backend/gporca/libgpopt/src/translate/CTranslatorExprToDXL.cpp @@ -179,6 +179,7 @@ #include "naucrates/dxl/operators/CDXLWindowFrame.h" #include "naucrates/dxl/operators/CDXLWindowKey.h" #include "naucrates/exception.h" +#include "naucrates/md/CMDIdGPDB.h" #include "naucrates/md/CMDRelationCtasGPDB.h" #include "naucrates/md/IMDCast.h" #include "naucrates/md/IMDFunction.h" @@ -4337,8 +4338,15 @@ CTranslatorExprToDXL::PdxlnCorrelatedNLJoin( CMDName *mdname = GPOS_NEW(m_mp) CMDName(m_mp, colref->Name().Pstr()); IMDId *mdid = colref->RetrieveType()->MDId(); mdid->AddRef(); + IMDId *mdid_coll = nullptr; + if (0 != colref->Collation()) + { + mdid_coll = GPOS_NEW(m_mp) + CMDIdGPDB(IMDId::EmdidGeneral, colref->Collation()); + } CDXLColRef *dxl_colref = GPOS_NEW(m_mp) - CDXLColRef(mdname, colref->Id(), mdid, colref->TypeModifier()); + CDXLColRef(mdname, colref->Id(), mdid, colref->TypeModifier(), + mdid_coll); dxl_colref_array->Append(dxl_colref); } @@ -4838,8 +4846,15 @@ CTranslatorExprToDXL::PdxlnNLJoin(CExpression *pexprInnerNLJ, GPOS_NEW(m_mp) CMDName(m_mp, col_ref->Name().Pstr()); IMDId *mdid = col_ref->RetrieveType()->MDId(); mdid->AddRef(); + IMDId *mdid_coll = nullptr; + if (0 != col_ref->Collation()) + { + mdid_coll = GPOS_NEW(m_mp) + CMDIdGPDB(IMDId::EmdidGeneral, col_ref->Collation()); + } CDXLColRef *colref_dxl = GPOS_NEW(m_mp) CDXLColRef( - md_name, col_ref->Id(), mdid, col_ref->TypeModifier()); + md_name, col_ref->Id(), mdid, col_ref->TypeModifier(), + mdid_coll); col_refs->Append(colref_dxl); } } @@ -5647,9 +5662,16 @@ CTranslatorExprToDXL::PdxlnCTAS(CExpression *pexpr, CMDIdGPDB::CastMdid(colref->RetrieveType()->MDId()); pmdidColType->AddRef(); + IMDId *mdid_collation = nullptr; + if (0 != pcd->Collation()) + { + mdid_collation = GPOS_NEW(m_mp) + CMDIdGPDB(IMDId::EmdidGeneral, pcd->Collation()); + } CDXLColDescr *pdxlcd = GPOS_NEW(m_mp) CDXLColDescr( pmdnameCol, colref->Id(), pcd->AttrNum(), pmdidColType, - colref->TypeModifier(), false /* fdropped */, pcd->Width()); + colref->TypeModifier(), false /* fdropped */, pcd->Width(), + mdid_collation); dxl_col_descr_array->Append(pdxlcd); } @@ -7366,9 +7388,16 @@ CTranslatorExprToDXL::MakeDXLTableDescr( CMDIdGPDB::CastMdid(colref->RetrieveType()->MDId()); pmdidColType->AddRef(); + IMDId *mdid_collation = nullptr; + if (0 != pcd->Collation()) + { + mdid_collation = GPOS_NEW(m_mp) + CMDIdGPDB(IMDId::EmdidGeneral, pcd->Collation()); + } CDXLColDescr *pdxlcd = GPOS_NEW(m_mp) CDXLColDescr( pmdnameCol, colref->Id(), pcd->AttrNum(), pmdidColType, - colref->TypeModifier(), false /* fdropped */, pcd->Width()); + colref->TypeModifier(), false /* fdropped */, pcd->Width(), + mdid_collation); table_descr->AddColumnDescr(pdxlcd); } diff --git a/src/backend/gporca/libgpopt/src/translate/CTranslatorExprToDXLUtils.cpp b/src/backend/gporca/libgpopt/src/translate/CTranslatorExprToDXLUtils.cpp index 27f5cb688fe..1292ea7167f 100644 --- a/src/backend/gporca/libgpopt/src/translate/CTranslatorExprToDXLUtils.cpp +++ b/src/backend/gporca/libgpopt/src/translate/CTranslatorExprToDXLUtils.cpp @@ -40,6 +40,7 @@ #include "naucrates/dxl/operators/CDXLScalarProjList.h" #include "naucrates/dxl/operators/CDXLScalarValuesList.h" #include "naucrates/exception.h" +#include "naucrates/md/CMDIdGPDB.h" #include "naucrates/md/IMDCast.h" #include "naucrates/md/IMDScalarOp.h" #include "naucrates/md/IMDTypeBool.h" @@ -418,8 +419,15 @@ CTranslatorExprToDXLUtils::PdxlnIdent(CMemoryPool *mp, IMDId *mdid = colref->RetrieveType()->MDId(); mdid->AddRef(); - CDXLColRef *dxl_colref = - GPOS_NEW(mp) CDXLColRef(mdname, colid, mdid, colref->TypeModifier()); + IMDId *mdid_collation = nullptr; + if (0 != colref->Collation()) + { + mdid_collation = + GPOS_NEW(mp) CMDIdGPDB(IMDId::EmdidGeneral, colref->Collation()); + } + + CDXLColRef *dxl_colref = GPOS_NEW(mp) + CDXLColRef(mdname, colid, mdid, colref->TypeModifier(), mdid_collation); CDXLScalarIdent *dxl_op = GPOS_NEW(mp) CDXLScalarIdent(mp, dxl_colref); return GPOS_NEW(mp) CDXLNode(mp, dxl_op); diff --git a/src/backend/gporca/libnaucrates/include/naucrates/dxl/operators/CDXLColDescr.h b/src/backend/gporca/libnaucrates/include/naucrates/dxl/operators/CDXLColDescr.h index b91bbbbb421..0cf7f10d224 100644 --- a/src/backend/gporca/libnaucrates/include/naucrates/dxl/operators/CDXLColDescr.h +++ b/src/backend/gporca/libnaucrates/include/naucrates/dxl/operators/CDXLColDescr.h @@ -61,13 +61,17 @@ class CDXLColDescr : public CRefCount // width of the column, for instance char(10) column has width 10 ULONG m_column_width; + // collation OID + IMDId *m_mdid_collation; + public: CDXLColDescr(const CDXLColDescr &) = delete; // ctor CDXLColDescr(CMDName *, ULONG column_id, INT attr_no, IMDId *column_mdid_type, INT type_modifier, BOOL is_dropped, - ULONG width = gpos::ulong_max); + ULONG width = gpos::ulong_max, + IMDId *mdid_collation = nullptr); //dtor ~CDXLColDescr() override; @@ -92,6 +96,9 @@ class CDXLColDescr : public CRefCount // column width ULONG Width() const; + // column collation + IMDId *MdidCollation() const; + void SerializeToDXL(CXMLSerializer *xml_serializer) const; }; diff --git a/src/backend/gporca/libnaucrates/include/naucrates/dxl/operators/CDXLColRef.h b/src/backend/gporca/libnaucrates/include/naucrates/dxl/operators/CDXLColRef.h index 4c1abdcd756..b9226a7628b 100644 --- a/src/backend/gporca/libnaucrates/include/naucrates/dxl/operators/CDXLColRef.h +++ b/src/backend/gporca/libnaucrates/include/naucrates/dxl/operators/CDXLColRef.h @@ -56,11 +56,15 @@ class CDXLColRef : public CRefCount // column type modifier INT m_iTypeModifer; + // column collation + IMDId *m_mdid_collation; + public: CDXLColRef(const CDXLColRef &) = delete; // ctor/dtor - CDXLColRef(CMDName *mdname, ULONG id, IMDId *mdid_type, INT type_modifier); + CDXLColRef(CMDName *mdname, ULONG id, IMDId *mdid_type, INT type_modifier, + IMDId *mdid_collation = nullptr); ~CDXLColRef() override; @@ -71,6 +75,8 @@ class CDXLColRef : public CRefCount INT TypeModifier() const; + IMDId *MdidCollation() const; + ULONG Id() const; }; } // namespace gpdxl diff --git a/src/backend/gporca/libnaucrates/include/naucrates/dxl/operators/CDXLScalarIdent.h b/src/backend/gporca/libnaucrates/include/naucrates/dxl/operators/CDXLScalarIdent.h index 865edf59db7..92f50fab9bf 100644 --- a/src/backend/gporca/libnaucrates/include/naucrates/dxl/operators/CDXLScalarIdent.h +++ b/src/backend/gporca/libnaucrates/include/naucrates/dxl/operators/CDXLScalarIdent.h @@ -57,6 +57,8 @@ class CDXLScalarIdent : public CDXLScalar INT TypeModifier() const; + IMDId *MdidCollation() const; + // serialize operator in DXL format void SerializeToDXL(CXMLSerializer *xml_serializer, const CDXLNode *node) const override; diff --git a/src/backend/gporca/libnaucrates/include/naucrates/dxl/parser/CParseHandlerMetadataColumn.h b/src/backend/gporca/libnaucrates/include/naucrates/dxl/parser/CParseHandlerMetadataColumn.h index 0fd218662a2..abb8493892e 100644 --- a/src/backend/gporca/libnaucrates/include/naucrates/dxl/parser/CParseHandlerMetadataColumn.h +++ b/src/backend/gporca/libnaucrates/include/naucrates/dxl/parser/CParseHandlerMetadataColumn.h @@ -58,6 +58,9 @@ class CParseHandlerMetadataColumn : public CParseHandlerBase // width of the column ULONG m_width; + // collation OID + OID m_collation; + // process the start of an element void StartElement( const XMLCh *const element_uri, // URI of element's namespace diff --git a/src/backend/gporca/libnaucrates/include/naucrates/dxl/xml/dxltokens.h b/src/backend/gporca/libnaucrates/include/naucrates/dxl/xml/dxltokens.h index 5a61801511a..71bc360ec42 100644 --- a/src/backend/gporca/libnaucrates/include/naucrates/dxl/xml/dxltokens.h +++ b/src/backend/gporca/libnaucrates/include/naucrates/dxl/xml/dxltokens.h @@ -296,6 +296,7 @@ enum Edxltoken // CoerceToDomain and CoerceViaIO and ArrayCoerceExpr related tokens EdxltokenTypeMod, + EdxltokenColCollation, EdxltokenCoercionForm, EdxltokenLocation, EdxltokenIsExplicit, diff --git a/src/backend/gporca/libnaucrates/include/naucrates/md/CMDColumn.h b/src/backend/gporca/libnaucrates/include/naucrates/md/CMDColumn.h index 74f72070ada..866294c42b4 100644 --- a/src/backend/gporca/libnaucrates/include/naucrates/md/CMDColumn.h +++ b/src/backend/gporca/libnaucrates/include/naucrates/md/CMDColumn.h @@ -60,13 +60,16 @@ class CMDColumn : public IMDColumn // length of the column ULONG m_length; + // collation OID + OID m_collation; + public: CMDColumn(const CMDColumn &) = delete; // ctor CMDColumn(CMDName *mdname, INT attrnum, IMDId *mdid_type, INT type_modifier, BOOL is_nullable, BOOL is_dropped, - ULONG length = gpos::ulong_max); + ULONG length = gpos::ulong_max, OID collation = 0); // dtor ~CMDColumn() override; @@ -96,6 +99,13 @@ class CMDColumn : public IMDColumn return m_length; } + // collation of the column + OID + Collation() const override + { + return m_collation; + } + // is the column nullable BOOL IsNullable() const override; diff --git a/src/backend/gporca/libnaucrates/include/naucrates/md/IMDColumn.h b/src/backend/gporca/libnaucrates/include/naucrates/md/IMDColumn.h index 4b7d9ad3841..dfdc3353a95 100644 --- a/src/backend/gporca/libnaucrates/include/naucrates/md/IMDColumn.h +++ b/src/backend/gporca/libnaucrates/include/naucrates/md/IMDColumn.h @@ -60,6 +60,9 @@ class IMDColumn : public IMDInterface // length of the column virtual ULONG Length() const = 0; + // collation of the column + virtual OID Collation() const = 0; + #ifdef GPOS_DEBUG // debug print of the column virtual void DebugPrint(IOstream &os) const = 0; diff --git a/src/backend/gporca/libnaucrates/src/md/CMDColumn.cpp b/src/backend/gporca/libnaucrates/src/md/CMDColumn.cpp index a5d8938aec7..39a52af0e78 100644 --- a/src/backend/gporca/libnaucrates/src/md/CMDColumn.cpp +++ b/src/backend/gporca/libnaucrates/src/md/CMDColumn.cpp @@ -29,14 +29,15 @@ using namespace gpmd; //--------------------------------------------------------------------------- CMDColumn::CMDColumn(CMDName *mdname, INT attrnum, IMDId *mdid_type, INT type_modifier, BOOL is_nullable, BOOL is_dropped, - ULONG length) + ULONG length, OID collation) : m_mdname(mdname), m_attno(attrnum), m_mdid_type(mdid_type), m_type_modifier(type_modifier), m_is_nullable(is_nullable), m_is_dropped(is_dropped), - m_length(length) + m_length(length), + m_collation(collation) { } @@ -173,6 +174,12 @@ CMDColumn::Serialize(CXMLSerializer *xml_serializer) const CDXLTokens::GetDXLTokenStr(EdxltokenColDropped), m_is_dropped); } + if (0 != m_collation) + { + xml_serializer->AddAttribute( + CDXLTokens::GetDXLTokenStr(EdxltokenColCollation), m_collation); + } + xml_serializer->CloseElement( CDXLTokens::GetDXLTokenStr(EdxltokenNamespacePrefix), CDXLTokens::GetDXLTokenStr(EdxltokenColumn)); diff --git a/src/backend/gporca/libnaucrates/src/operators/CDXLColDescr.cpp b/src/backend/gporca/libnaucrates/src/operators/CDXLColDescr.cpp index 1afdd2990d8..fe361dcf0a6 100644 --- a/src/backend/gporca/libnaucrates/src/operators/CDXLColDescr.cpp +++ b/src/backend/gporca/libnaucrates/src/operators/CDXLColDescr.cpp @@ -30,14 +30,16 @@ using namespace gpmd; //--------------------------------------------------------------------------- CDXLColDescr::CDXLColDescr(CMDName *md_name, ULONG column_id, INT attr_no, IMDId *column_mdid_type, INT type_modifier, - BOOL is_dropped, ULONG width) + BOOL is_dropped, ULONG width, + IMDId *mdid_collation) : m_md_name(md_name), m_column_id(column_id), m_attr_no(attr_no), m_column_mdid_type(column_mdid_type), m_type_modifier(type_modifier), m_is_dropped(is_dropped), - m_column_width(width) + m_column_width(width), + m_mdid_collation(mdid_collation) { GPOS_ASSERT_IMP(m_is_dropped, 0 == m_md_name->GetMDName()->Length()); } @@ -53,6 +55,10 @@ CDXLColDescr::CDXLColDescr(CMDName *md_name, ULONG column_id, INT attr_no, CDXLColDescr::~CDXLColDescr() { m_column_mdid_type->Release(); + if (m_mdid_collation) + { + m_mdid_collation->Release(); + } GPOS_DELETE(m_md_name); } @@ -146,6 +152,12 @@ CDXLColDescr::Width() const return m_column_width; } +IMDId * +CDXLColDescr::MdidCollation() const +{ + return m_mdid_collation; +} + //--------------------------------------------------------------------------- // @function: // CDXLColDescr::SerializeToDXL @@ -191,6 +203,13 @@ CDXLColDescr::SerializeToDXL(CXMLSerializer *xml_serializer) const CDXLTokens::GetDXLTokenStr(EdxltokenColWidth), m_column_width); } + if (nullptr != m_mdid_collation && m_mdid_collation->IsValid()) + { + m_mdid_collation->Serialize( + xml_serializer, + CDXLTokens::GetDXLTokenStr(EdxltokenColCollation)); + } + xml_serializer->CloseElement( CDXLTokens::GetDXLTokenStr(EdxltokenNamespacePrefix), pstrTokenColDescr); diff --git a/src/backend/gporca/libnaucrates/src/operators/CDXLColRef.cpp b/src/backend/gporca/libnaucrates/src/operators/CDXLColRef.cpp index a29f074dd85..c3e793af3dc 100644 --- a/src/backend/gporca/libnaucrates/src/operators/CDXLColRef.cpp +++ b/src/backend/gporca/libnaucrates/src/operators/CDXLColRef.cpp @@ -26,11 +26,12 @@ using namespace gpdxl; // //--------------------------------------------------------------------------- CDXLColRef::CDXLColRef(CMDName *mdname, ULONG id, IMDId *mdid_type, - INT type_modifier) + INT type_modifier, IMDId *mdid_collation) : m_mdname(mdname), m_id(id), m_mdid_type(mdid_type), - m_iTypeModifer(type_modifier) + m_iTypeModifer(type_modifier), + m_mdid_collation(mdid_collation) { GPOS_ASSERT(m_mdid_type->IsValid()); } @@ -47,6 +48,10 @@ CDXLColRef::~CDXLColRef() { GPOS_DELETE(m_mdname); m_mdid_type->Release(); + if (m_mdid_collation) + { + m_mdid_collation->Release(); + } } //--------------------------------------------------------------------------- @@ -83,6 +88,12 @@ CDXLColRef::TypeModifier() const return m_iTypeModifer; } +IMDId * +CDXLColRef::MdidCollation() const +{ + return m_mdid_collation; +} + //--------------------------------------------------------------------------- // @function: // CDXLColRef::Id diff --git a/src/backend/gporca/libnaucrates/src/operators/CDXLOperatorFactory.cpp b/src/backend/gporca/libnaucrates/src/operators/CDXLOperatorFactory.cpp index 8b037b833f0..28b381f4c45 100644 --- a/src/backend/gporca/libnaucrates/src/operators/CDXLOperatorFactory.cpp +++ b/src/backend/gporca/libnaucrates/src/operators/CDXLOperatorFactory.cpp @@ -1683,8 +1683,20 @@ CDXLOperatorFactory::MakeColumnDescr(CDXLMemoryManager *dxl_memory_manager, GPOS_DELETE(col_name); + // parse optional collation + IMDId *mdid_collation = nullptr; + const XMLCh *collation_xml = + attrs.getValue(CDXLTokens::XmlstrToken(EdxltokenColCollation)); + if (nullptr != collation_xml) + { + mdid_collation = ExtractConvertAttrValueToMdId( + dxl_memory_manager, attrs, EdxltokenColCollation, + EdxltokenColDescr); + } + return GPOS_NEW(mp) CDXLColDescr(mdname, id, attno, mdid_type, - type_modifier, col_dropped, col_len); + type_modifier, col_dropped, col_len, + mdid_collation); } //--------------------------------------------------------------------------- @@ -1736,7 +1748,18 @@ CDXLOperatorFactory::MakeDXLColRef(CDXLMemoryManager *dxl_memory_manager, dxl_memory_manager, attrs, EdxltokenTypeMod, target_elem, true, default_type_modifier); - return GPOS_NEW(mp) CDXLColRef(mdname, id, mdid_type, type_modifier); + // parse optional collation + IMDId *mdid_collation = nullptr; + const XMLCh *collation_xml = + attrs.getValue(CDXLTokens::XmlstrToken(EdxltokenColCollation)); + if (nullptr != collation_xml) + { + mdid_collation = ExtractConvertAttrValueToMdId( + dxl_memory_manager, attrs, EdxltokenColCollation, target_elem); + } + + return GPOS_NEW(mp) + CDXLColRef(mdname, id, mdid_type, type_modifier, mdid_collation); } //--------------------------------------------------------------------------- diff --git a/src/backend/gporca/libnaucrates/src/operators/CDXLScalarIdent.cpp b/src/backend/gporca/libnaucrates/src/operators/CDXLScalarIdent.cpp index bb6f4b1659b..14187051ee5 100644 --- a/src/backend/gporca/libnaucrates/src/operators/CDXLScalarIdent.cpp +++ b/src/backend/gporca/libnaucrates/src/operators/CDXLScalarIdent.cpp @@ -112,6 +112,12 @@ CDXLScalarIdent::TypeModifier() const return m_dxl_colref->TypeModifier(); } +IMDId * +CDXLScalarIdent::MdidCollation() const +{ + return m_dxl_colref->MdidCollation(); +} + //--------------------------------------------------------------------------- // @function: // CDXLScalarIdent::SerializeToDXL @@ -145,6 +151,13 @@ CDXLScalarIdent::SerializeToDXL(CXMLSerializer *xml_serializer, CDXLTokens::GetDXLTokenStr(EdxltokenTypeMod), TypeModifier()); } + if (nullptr != MdidCollation() && MdidCollation()->IsValid()) + { + MdidCollation()->Serialize( + xml_serializer, + CDXLTokens::GetDXLTokenStr(EdxltokenColCollation)); + } + node->SerializeChildrenToDXL(xml_serializer); xml_serializer->CloseElement( diff --git a/src/backend/gporca/libnaucrates/src/parser/CParseHandlerMetadataColumn.cpp b/src/backend/gporca/libnaucrates/src/parser/CParseHandlerMetadataColumn.cpp index 6881f8fb411..e7025a509f1 100644 --- a/src/backend/gporca/libnaucrates/src/parser/CParseHandlerMetadataColumn.cpp +++ b/src/backend/gporca/libnaucrates/src/parser/CParseHandlerMetadataColumn.cpp @@ -38,7 +38,8 @@ CParseHandlerMetadataColumn::CParseHandlerMetadataColumn( m_mdcol(nullptr), m_mdname(nullptr), m_mdid_type(nullptr), - m_width(gpos::ulong_max) + m_width(gpos::ulong_max), + m_collation(0) { } @@ -130,6 +131,16 @@ CParseHandlerMetadataColumn::StartElement(const XMLCh *const, // element_uri, m_parse_handler_mgr->GetDXLMemoryManager(), xmlszDropped, EdxltokenColDropped, EdxltokenMetadataColumn); } + + // parse optional collation + const XMLCh *collation_xml = + attrs.getValue(CDXLTokens::XmlstrToken(EdxltokenColCollation)); + if (nullptr != collation_xml) + { + m_collation = CDXLOperatorFactory::ConvertAttrValueToUlong( + m_parse_handler_mgr->GetDXLMemoryManager(), collation_xml, + EdxltokenColCollation, EdxltokenMetadataColumn); + } } //--------------------------------------------------------------------------- @@ -157,7 +168,7 @@ CParseHandlerMetadataColumn::EndElement(const XMLCh *const, // element_uri, m_mdcol = GPOS_NEW(m_mp) CMDColumn(m_mdname, m_attno, m_mdid_type, m_type_modifier, - m_is_nullable, m_is_dropped, m_width); + m_is_nullable, m_is_dropped, m_width, m_collation); // deactivate handler m_parse_handler_mgr->DeactivateHandler(); diff --git a/src/backend/gporca/libnaucrates/src/xml/dxltokens.cpp b/src/backend/gporca/libnaucrates/src/xml/dxltokens.cpp index 0f59d689a05..c0471e0ba4e 100644 --- a/src/backend/gporca/libnaucrates/src/xml/dxltokens.cpp +++ b/src/backend/gporca/libnaucrates/src/xml/dxltokens.cpp @@ -288,6 +288,7 @@ CDXLTokens::Init(CMemoryPool *mp) {EdxltokenDatum, GPOS_WSZ_LIT("Datum")}, {EdxltokenTypeMod, GPOS_WSZ_LIT("TypeModifier")}, + {EdxltokenColCollation, GPOS_WSZ_LIT("CollationMdid")}, {EdxltokenCoercionForm, GPOS_WSZ_LIT("CoercionForm")}, {EdxltokenLocation, GPOS_WSZ_LIT("Location")}, {EdxltokenIsExplicit, GPOS_WSZ_LIT("IsExplicit")}, diff --git a/src/backend/optimizer/util/walkers.c b/src/backend/optimizer/util/walkers.c index 3b3d0311d06..13f06bf5ddd 100644 --- a/src/backend/optimizer/util/walkers.c +++ b/src/backend/optimizer/util/walkers.c @@ -13,6 +13,7 @@ #include "miscadmin.h" #include "nodes/nodeFuncs.h" #include "optimizer/walkers.h" +#include "utils/lsyscache.h" /** * Plan node walker related methods. @@ -892,7 +893,8 @@ check_collation_in_list(List *colllist, check_collation_context *context) foreach (lc, colllist) { Oid coll = lfirst_oid(lc); - if (InvalidOid != coll && DEFAULT_COLLATION_OID != coll) + if (InvalidOid != coll && DEFAULT_COLLATION_OID != coll && + C_COLLATION_OID != coll) { context->foundNonDefaultCollation = 1; break; @@ -916,10 +918,60 @@ check_collation_walker(Node *node, check_collation_context *context) return query_tree_walker((Query *) node, check_collation_walker, (void *) context, 0 /* flags */); } + /* + * ORCA cannot propagate collation through SubPlan boundaries + * (CColRefComputed doesn't carry collation). When a set-returning + * function (e.g. unnest) takes a SubLink argument whose output has + * non-default collation (including C), the expanded rows lose their + * collation, causing wrong sort order in collation-sensitive operations. + * Fall back in this case. + * + * The SubLink may be wrapped in coercion nodes (RelabelType, + * ArrayCoerceExpr, CoerceViaIO, etc.) so we strip those to find it. + */ + if (IsA(node, FuncExpr)) + { + FuncExpr *func = (FuncExpr *) node; + if (func->funcretset) + { + ListCell *lc; + foreach(lc, func->args) + { + Node *arg = lfirst(lc); + /* Strip coercion wrappers to find a buried SubLink */ + for (;;) + { + if (arg == NULL) + break; + if (IsA(arg, SubLink)) + break; + if (IsA(arg, RelabelType)) + arg = (Node *) ((RelabelType *) arg)->arg; + else if (IsA(arg, ArrayCoerceExpr)) + arg = (Node *) ((ArrayCoerceExpr *) arg)->arg; + else if (IsA(arg, CoerceViaIO)) + arg = (Node *) ((CoerceViaIO *) arg)->arg; + else if (IsA(arg, ConvertRowtypeExpr)) + arg = (Node *) ((ConvertRowtypeExpr *) arg)->arg; + else + break; + } + if (arg && IsA(arg, SubLink)) + { + Oid coll = exprCollation(arg); + if (OidIsValid(coll) && coll != DEFAULT_COLLATION_OID) + { + context->foundNonDefaultCollation = 1; + break; + } + } + } + } + } + switch (nodeTag(node)) { case T_Var: - case T_Const: case T_OpExpr: type = exprType((node)); collation = exprCollation(node); @@ -928,11 +980,35 @@ check_collation_walker(Node *node, check_collation_context *context) if (collation != C_COLLATION_OID) context->foundNonDefaultCollation = 1; } - else if (InvalidOid != collation && DEFAULT_COLLATION_OID != collation) + else if (InvalidOid != collation && DEFAULT_COLLATION_OID != collation && + C_COLLATION_OID != collation) + { + context->foundNonDefaultCollation = 1; + } + break; + case T_Const: + { + /* + * fold_constants() converts CollateExpr on a constant into a + * Const with modified constcollid (no RelabelType wrapper). + * Normal string constants have constcollid = DEFAULT_COLLATION_OID. + * Constants with explicit COLLATE (e.g., 'foo' COLLATE "C") + * have a different constcollid that ORCA cannot propagate. + * + * To distinguish explicit COLLATE from inherent type collation + * (e.g., '1505703298'::name where name type has C collation), + * compare constcollid with the type's default collation. + * A mismatch means an explicit COLLATE override was applied. + */ + Const *c = (Const *) node; + if (OidIsValid(c->constcollid) && + c->constcollid != DEFAULT_COLLATION_OID && + c->constcollid != get_typcollation(c->consttype)) { context->foundNonDefaultCollation = 1; } break; + } case T_ScalarArrayOpExpr: case T_DistinctExpr: case T_BoolExpr: @@ -946,7 +1022,6 @@ check_collation_walker(Node *node, check_collation_context *context) case T_WindowFunc: case T_NullTest: case T_NullIfExpr: - case T_RelabelType: case T_CoerceToDomain: case T_CoerceViaIO: case T_ArrayCoerceExpr: @@ -973,14 +1048,57 @@ check_collation_walker(Node *node, check_collation_context *context) case T_DMLActionExpr: collation = exprCollation(node); inputCollation = exprInputCollation(node); - if ((InvalidOid != collation && DEFAULT_COLLATION_OID != collation) || - (InvalidOid != inputCollation && DEFAULT_COLLATION_OID != inputCollation)) + if ((InvalidOid != collation && DEFAULT_COLLATION_OID != collation && + C_COLLATION_OID != collation) || + (InvalidOid != inputCollation && DEFAULT_COLLATION_OID != inputCollation && + C_COLLATION_OID != inputCollation)) { context->foundNonDefaultCollation = 1; } break; + case T_RelabelType: + { + /* + * fold_constants() converts CollateExpr to RelabelType. + * Detect expression-level COLLATE override by checking if the + * RelabelType's collation differs from its argument's collation. + * ORCA does not yet propagate expression-level collation, so + * these must fall back to the Postgres planner. + * + * Skip the check when the argument's collation is InvalidOid. + * This happens for CaseTestExpr inside ArrayCoerceExpr, where + * the parser leaves the placeholder's collation unset. + * Treating that as a mismatch would cause false fallback for + * ordinary casts like text[]::varchar[]. + */ + RelabelType *r = (RelabelType *) node; + Oid arg_coll = exprCollation((Node *) r->arg); + if (OidIsValid(r->resultcollid) && + OidIsValid(arg_coll) && + r->resultcollid != arg_coll) + { + context->foundNonDefaultCollation = 1; + break; + } + /* Normal type coercion: check like other expression nodes */ + collation = exprCollation(node); + inputCollation = exprInputCollation(node); + if ((InvalidOid != collation && DEFAULT_COLLATION_OID != collation && + C_COLLATION_OID != collation) || + (InvalidOid != inputCollation && DEFAULT_COLLATION_OID != inputCollation && + C_COLLATION_OID != inputCollation)) + { + context->foundNonDefaultCollation = 1; + } + break; + } case T_CollateClause: - /* unsupported */ + /* + * CollateClause is a raw parse node without a resolved OID. + * Analyzed COLLATE expressions use T_CollateExpr instead, + * which is handled above with the C_COLLATION_OID check. + * Keep this as unconditional reject for safety. + */ context->foundNonDefaultCollation = 1; break; case T_RangeTblEntry: diff --git a/src/include/gpopt/gpdbwrappers.h b/src/include/gpopt/gpdbwrappers.h index 261cd28b5f0..4470d053248 100644 --- a/src/include/gpopt/gpdbwrappers.h +++ b/src/include/gpopt/gpdbwrappers.h @@ -529,7 +529,7 @@ TargetEntry *MakeTargetEntry(Expr *expr, AttrNumber resno, char *resname, // create a new var node Var *MakeVar(Index varno, AttrNumber varattno, Oid vartype, int32 vartypmod, - Index varlevelsup); + Oid varcollid, Index varlevelsup); // memory allocation functions void *MemCtxtAllocZeroAligned(MemoryContext context, Size size); diff --git a/src/test/regress/expected/aggregates_optimizer.out b/src/test/regress/expected/aggregates_optimizer.out index 73fcea90841..fe9f59f719d 100644 --- a/src/test/regress/expected/aggregates_optimizer.out +++ b/src/test/regress/expected/aggregates_optimizer.out @@ -2075,8 +2075,6 @@ from generate_series(1,6) x; (1 row) select ten, mode() within group (order by string4) from tenk1 group by ten order by ten; -INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation ten | mode -----+-------- 0 | HHHHxx @@ -2554,8 +2552,6 @@ from generate_series(1,6) x; (1 row) select ten, mode() within group (order by string4) from tenk1 group by ten; -INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation ten | mode -----+-------- 0 | HHHHxx @@ -3085,9 +3081,7 @@ FROM (SELECT * FROM tenk1 UNION ALL SELECT * FROM tenk1 UNION ALL SELECT * FROM tenk1 UNION ALL SELECT * FROM tenk1) u; -INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation - QUERY PLAN + QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Finalize Aggregate Output: variance(tenk1.unique1), sum((tenk1.unique1)::bigint), regr_count((tenk1.unique1)::double precision, (tenk1.unique1)::double precision) @@ -3113,8 +3107,6 @@ FROM (SELECT * FROM tenk1 UNION ALL SELECT * FROM tenk1 UNION ALL SELECT * FROM tenk1 UNION ALL SELECT * FROM tenk1) u; -INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation variance | sum | regr_count ----------------------+-----------+------------ 8333541.588539713493 | 199980000 | 40000 @@ -3128,9 +3120,7 @@ FROM (SELECT * FROM tenk1 UNION ALL SELECT * FROM tenk1 UNION ALL SELECT * FROM tenk1 UNION ALL SELECT * FROM tenk1) u; -INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation - QUERY PLAN + QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------- Finalize Aggregate Output: variance((tenk1.unique1)::bigint), avg((tenk1.unique1)::numeric) @@ -3156,8 +3146,6 @@ FROM (SELECT * FROM tenk1 UNION ALL SELECT * FROM tenk1 UNION ALL SELECT * FROM tenk1 UNION ALL SELECT * FROM tenk1) u; -INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation variance | avg ----------------------+----------------------- 8333541.588539713493 | 4999.5000000000000000 diff --git a/src/test/regress/expected/btree_index_optimizer.out b/src/test/regress/expected/btree_index_optimizer.out new file mode 100644 index 00000000000..5e11dd5fd58 --- /dev/null +++ b/src/test/regress/expected/btree_index_optimizer.out @@ -0,0 +1,479 @@ +-- +-- BTREE_INDEX +-- test retrieval of min/max keys for each index +-- +SELECT b.* + FROM bt_i4_heap b + WHERE b.seqno < 1; + seqno | random +-------+------------ + 0 | 1935401906 +(1 row) + +SELECT b.* + FROM bt_i4_heap b + WHERE b.seqno >= 9999; + seqno | random +-------+------------ + 9999 | 1227676208 +(1 row) + +SELECT b.* + FROM bt_i4_heap b + WHERE b.seqno = 4500; + seqno | random +-------+------------ + 4500 | 2080851358 +(1 row) + +SELECT b.* + FROM bt_name_heap b + WHERE b.seqno < '1'::name; + seqno | random +-------+------------ + 0 | 1935401906 +(1 row) + +SELECT b.* + FROM bt_name_heap b + WHERE b.seqno >= '9999'::name; + seqno | random +-------+------------ + 9999 | 1227676208 +(1 row) + +SELECT b.* + FROM bt_name_heap b + WHERE b.seqno = '4500'::name; + seqno | random +-------+------------ + 4500 | 2080851358 +(1 row) + +SELECT b.* + FROM bt_txt_heap b + WHERE b.seqno < '1'::text; + seqno | random +-------+------------ + 0 | 1935401906 +(1 row) + +SELECT b.* + FROM bt_txt_heap b + WHERE b.seqno >= '9999'::text; + seqno | random +-------+------------ + 9999 | 1227676208 +(1 row) + +SELECT b.* + FROM bt_txt_heap b + WHERE b.seqno = '4500'::text; + seqno | random +-------+------------ + 4500 | 2080851358 +(1 row) + +SELECT b.* + FROM bt_f8_heap b + WHERE b.seqno < '1'::float8; + seqno | random +-------+------------ + 0 | 1935401906 +(1 row) + +SELECT b.* + FROM bt_f8_heap b + WHERE b.seqno >= '9999'::float8; + seqno | random +-------+------------ + 9999 | 1227676208 +(1 row) + +SELECT b.* + FROM bt_f8_heap b + WHERE b.seqno = '4500'::float8; + seqno | random +-------+------------ + 4500 | 2080851358 +(1 row) + +-- +-- Check correct optimization of LIKE (special index operator support) +-- for both indexscan and bitmapscan cases +-- +set enable_seqscan to false; +set enable_indexscan to true; +set enable_bitmapscan to false; +set enable_sort to false; -- GPDB needs more strong-arming to get same plans as upstream +explain (costs off) +select proname from pg_proc where proname like E'RI\\_FKey%del' order by 1; + QUERY PLAN +------------------------------------------------------------------------------ + Index Only Scan using pg_proc_proname_args_nsp_index on pg_proc + Index Cond: ((proname >= 'RI_FKey'::text) AND (proname < 'RI_FKez'::text)) + Filter: (proname ~~ 'RI\_FKey%del'::text) + Optimizer: Postgres query optimizer +(4 rows) + +select proname from pg_proc where proname like E'RI\\_FKey%del' order by 1; + proname +------------------------ + RI_FKey_cascade_del + RI_FKey_noaction_del + RI_FKey_restrict_del + RI_FKey_setdefault_del + RI_FKey_setnull_del +(5 rows) + +explain (costs off) +select proname from pg_proc where proname ilike '00%foo' order by 1; + QUERY PLAN +-------------------------------------------------------------------- + Index Only Scan using pg_proc_proname_args_nsp_index on pg_proc + Index Cond: ((proname >= '00'::text) AND (proname < '01'::text)) + Filter: (proname ~~* '00%foo'::text) + Optimizer: Postgres query optimizer +(4 rows) + +select proname from pg_proc where proname ilike '00%foo' order by 1; + proname +--------- +(0 rows) + +explain (costs off) +select proname from pg_proc where proname ilike 'ri%foo' order by 1; + QUERY PLAN +----------------------------------------------------------------- + Index Only Scan using pg_proc_proname_args_nsp_index on pg_proc + Filter: (proname ~~* 'ri%foo'::text) + Optimizer: Postgres query optimizer +(3 rows) + +set enable_indexscan to false; +set enable_bitmapscan to true; +reset enable_sort; +explain (costs off) +select proname from pg_proc where proname like E'RI\\_FKey%del' order by 1; + QUERY PLAN +------------------------------------------------------------------------------------------ + Sort + Sort Key: proname + -> Bitmap Heap Scan on pg_proc + Filter: (proname ~~ 'RI\_FKey%del'::text) + -> Bitmap Index Scan on pg_proc_proname_args_nsp_index + Index Cond: ((proname >= 'RI_FKey'::text) AND (proname < 'RI_FKez'::text)) + Optimizer: Postgres query optimizer +(7 rows) + +select proname from pg_proc where proname like E'RI\\_FKey%del' order by 1; + proname +------------------------ + RI_FKey_cascade_del + RI_FKey_noaction_del + RI_FKey_restrict_del + RI_FKey_setdefault_del + RI_FKey_setnull_del +(5 rows) + +explain (costs off) +select proname from pg_proc where proname ilike '00%foo' order by 1; + QUERY PLAN +-------------------------------------------------------------------------------- + Sort + Sort Key: proname + -> Bitmap Heap Scan on pg_proc + Filter: (proname ~~* '00%foo'::text) + -> Bitmap Index Scan on pg_proc_proname_args_nsp_index + Index Cond: ((proname >= '00'::text) AND (proname < '01'::text)) + Optimizer: Postgres query optimizer +(7 rows) + +select proname from pg_proc where proname ilike '00%foo' order by 1; + proname +--------- +(0 rows) + +set enable_sort to false; -- GPDB needs more strong-arming to get same plans as upstream +set enable_bitmapscan to false; +explain (costs off) +select proname from pg_proc where proname ilike 'ri%foo' order by 1; + QUERY PLAN +----------------------------------------------------------------- + Index Only Scan using pg_proc_proname_args_nsp_index on pg_proc + Filter: (proname ~~* 'ri%foo'::text) + Optimizer: Postgres query optimizer +(3 rows) + +reset enable_seqscan; +reset enable_indexscan; +reset enable_bitmapscan; +reset enable_sort; +-- Also check LIKE optimization with binary-compatible cases +create temp table btree_bpchar (f1 text collate "C"); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'f1' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. +create index on btree_bpchar(f1 bpchar_ops) WITH (deduplicate_items=on); +insert into btree_bpchar values ('foo'), ('foo '), ('fool'), ('bar'), ('quux'); +-- doesn't match index: +explain (costs off) +select * from btree_bpchar where f1 like 'foo'; + QUERY PLAN +------------------------------------------ + Gather Motion 3:1 (slice1; segments: 3) + -> Seq Scan on btree_bpchar + Filter: (f1 ~~ 'foo'::text) + Optimizer: GPORCA +(4 rows) + +select * from btree_bpchar where f1 like 'foo'; + f1 +----- + foo +(1 row) + +explain (costs off) +select * from btree_bpchar where f1 like 'foo%'; + QUERY PLAN +------------------------------------------ + Gather Motion 3:1 (slice1; segments: 3) + -> Seq Scan on btree_bpchar + Filter: (f1 ~~ 'foo%'::text) + Optimizer: GPORCA +(4 rows) + +select * from btree_bpchar where f1 like 'foo%'; + f1 +------- + foo + foo + fool +(3 rows) + +-- these do match the index: +explain (costs off) +select * from btree_bpchar where f1::bpchar like 'foo'; + QUERY PLAN +----------------------------------------------- + Gather Motion 3:1 (slice1; segments: 3) + -> Seq Scan on btree_bpchar + Filter: ((f1)::bpchar ~~ 'foo'::text) + Optimizer: GPORCA +(4 rows) + +select * from btree_bpchar where f1::bpchar like 'foo'; + f1 +----- + foo +(1 row) + +explain (costs off) +select * from btree_bpchar where f1::bpchar like 'foo%'; + QUERY PLAN +------------------------------------------------ + Gather Motion 3:1 (slice1; segments: 3) + -> Seq Scan on btree_bpchar + Filter: ((f1)::bpchar ~~ 'foo%'::text) + Optimizer: GPORCA +(4 rows) + +select * from btree_bpchar where f1::bpchar like 'foo%'; + f1 +------- + foo + fool + foo +(3 rows) + +explain (costs off) +select * from btree_bpchar where f1::bpchar ='foo'; + QUERY PLAN +------------------------------------------------------------ + Gather Motion 3:1 (slice1; segments: 3) + -> Index Scan using btree_bpchar_f1_idx on btree_bpchar + Index Cond: ((f1)::bpchar = 'foo'::bpchar) + Optimizer: GPORCA +(4 rows) + +select * from btree_bpchar where f1::bpchar ='foo'; + f1 +------- + foo + foo +(2 rows) + +-- get test coverage for "single value" deduplication strategy: +insert into btree_bpchar select 'foo' from generate_series(1,1500); +-- +-- Perform unique checking, with and without the use of deduplication +-- +CREATE TABLE dedup_unique_test_table (a int) WITH (autovacuum_enabled=false); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. +CREATE UNIQUE INDEX dedup_unique ON dedup_unique_test_table (a) WITH (deduplicate_items=on); +CREATE UNIQUE INDEX plain_unique ON dedup_unique_test_table (a) WITH (deduplicate_items=off); +-- Generate enough garbage tuples in index to ensure that even the unique index +-- with deduplication enabled has to check multiple leaf pages during unique +-- checking (at least with a BLCKSZ of 8192 or less) +DO $$ +BEGIN + FOR r IN 1..1350 LOOP + DELETE FROM dedup_unique_test_table; + INSERT INTO dedup_unique_test_table SELECT 1; + END LOOP; +END$$; +-- Exercise the LP_DEAD-bit-set tuple deletion code with a posting list tuple. +-- The implementation prefers deleting existing items to merging any duplicate +-- tuples into a posting list, so we need an explicit test to make sure we get +-- coverage (note that this test also assumes BLCKSZ is 8192 or less): +DROP INDEX plain_unique; +DELETE FROM dedup_unique_test_table WHERE a = 1; +INSERT INTO dedup_unique_test_table SELECT i FROM generate_series(0,450) i; +-- +-- Test B-tree fast path (cache rightmost leaf page) optimization. +-- +-- First create a tree that's at least three levels deep (i.e. has one level +-- between the root and leaf levels). The text inserted is long. It won't be +-- TOAST compressed because we use plain storage in the table. Only a few +-- index tuples fit on each internal page, allowing us to get a tall tree with +-- few pages. (A tall tree is required to trigger caching.) +-- +-- The text column must be the leading column in the index, since suffix +-- truncation would otherwise truncate tuples on internal pages, leaving us +-- with a short tree. +create table btree_tall_tbl(id int4, t text); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'id' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. +alter table btree_tall_tbl alter COLUMN t set storage plain; +create index btree_tall_idx on btree_tall_tbl (t, id) with (fillfactor = 10); +insert into btree_tall_tbl select g, repeat('x', 250) +from generate_series(1, 130) g; +-- +-- Test for multilevel page deletion +-- +CREATE TABLE delete_test_table (a bigint, b bigint, c bigint, d bigint); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. +INSERT INTO delete_test_table SELECT i, 1, 2, 3 FROM generate_series(1,80000) i; +ALTER TABLE delete_test_table ADD PRIMARY KEY (a,b,c,d); +-- Delete most entries, and vacuum, deleting internal pages and creating "fast +-- root" +DELETE FROM delete_test_table WHERE a < 79990; +VACUUM delete_test_table; +-- +-- Test B-tree insertion with a metapage update (XLOG_BTREE_INSERT_META +-- WAL record type). This happens when a "fast root" page is split. This +-- also creates coverage for nbtree FSM page recycling. +-- +-- The vacuum above should've turned the leaf page into a fast root. We just +-- need to insert some rows to cause the fast root page to split. +INSERT INTO delete_test_table SELECT i, 1, 2, 3 FROM generate_series(1,1000) i; +-- +-- GPDB: Test correctness of B-tree stats in consecutively VACUUM. +-- +CREATE TABLE btree_stats_tbl(col_int int, col_text text, col_numeric numeric, col_unq int) DISTRIBUTED BY (col_int); +CREATE INDEX btree_stats_idx ON btree_stats_tbl(col_int); +INSERT INTO btree_stats_tbl VALUES (1, 'aa', 1001, 101), (2, 'bb', 1002, 102); +SELECT reltuples FROM pg_class WHERE relname='btree_stats_tbl'; + reltuples +----------- + -1 +(1 row) + +-- inspect the state of the stats on segments +SELECT gp_segment_id, relname, reltuples FROM gp_dist_random('pg_class') WHERE relname = 'btree_stats_idx'; + gp_segment_id | relname | reltuples +---------------+-----------------+----------- + 1 | btree_stats_idx | 0 + 2 | btree_stats_idx | 0 + 0 | btree_stats_idx | 0 +(3 rows) + +SELECT reltuples FROM pg_class WHERE relname='btree_stats_idx'; + reltuples +----------- + 0 +(1 row) + +-- 1st VACUUM, expect reltuples = 2 +vacuum btree_stats_tbl; +SELECT reltuples FROM pg_class WHERE relname='btree_stats_tbl'; + reltuples +----------- + 2 +(1 row) + +-- inspect the state of the stats on segments +SELECT gp_segment_id, relname, reltuples FROM gp_dist_random('pg_class') WHERE relname = 'btree_stats_idx'; + gp_segment_id | relname | reltuples +---------------+-----------------+----------- + 0 | btree_stats_idx | 1 + 2 | btree_stats_idx | 0 + 1 | btree_stats_idx | 1 +(3 rows) + +SELECT reltuples FROM pg_class WHERE relname='btree_stats_idx'; + reltuples +----------- + 2 +(1 row) + +-- 2nd VACUUM, expect reltuples = 2 +vacuum btree_stats_tbl; +SELECT reltuples FROM pg_class WHERE relname='btree_stats_tbl'; + reltuples +----------- + 2 +(1 row) + +-- inspect the state of the stats on segments +SELECT gp_segment_id, relname, reltuples FROM gp_dist_random('pg_class') WHERE relname = 'btree_stats_idx'; + gp_segment_id | relname | reltuples +---------------+-----------------+----------- + 1 | btree_stats_idx | 1 + 2 | btree_stats_idx | 0 + 0 | btree_stats_idx | 1 +(3 rows) + +SELECT reltuples FROM pg_class WHERE relname='btree_stats_idx'; + reltuples +----------- + 2 +(1 row) + +-- Prior to this fix, the case would be failed here. Given the +-- scenario of updating stats during VACUUM: +-- 1) coordinator vacuums and updates stats of its own; +-- 2) then coordinator dispatches vacuum to segments; +-- 3) coordinator combines stats received from segments to overwrite the stats of its own. +-- Because upstream introduced a feature which could skip full index scan uring cleanup +-- of B-tree indexes when possible (refer to: +-- https://github.com/postgres/postgres/commit/857f9c36cda520030381bd8c2af20adf0ce0e1d4), +-- there was a case in QD-QEs distributed deployment that some QEs could skip full index scan and +-- stop updating statistics, result in QD being unable to collect all QEs' stats thus overwrote +-- a paritial accumulated value to index->reltuples. More interesting, it usually happened starting +-- from the 3rd time of consecutively VACUUM after fresh inserts due to above skipping index scan +-- criteria. +-- 3rd VACUUM, expect reltuples = 2 +vacuum btree_stats_tbl; +SELECT reltuples FROM pg_class WHERE relname='btree_stats_tbl'; + reltuples +----------- + 2 +(1 row) + +-- inspect the state of the stats on segments +SELECT gp_segment_id, relname, reltuples FROM gp_dist_random('pg_class') WHERE relname = 'btree_stats_idx'; + gp_segment_id | relname | reltuples +---------------+-----------------+----------- + 0 | btree_stats_idx | 1 + 2 | btree_stats_idx | 0 + 1 | btree_stats_idx | 1 +(3 rows) + +SELECT reltuples FROM pg_class WHERE relname='btree_stats_idx'; + reltuples +----------- + 2 +(1 row) + diff --git a/src/test/regress/expected/cluster_optimizer.out b/src/test/regress/expected/cluster_optimizer.out new file mode 100644 index 00000000000..ec7c3a36d7b --- /dev/null +++ b/src/test/regress/expected/cluster_optimizer.out @@ -0,0 +1,656 @@ +-- +-- CLUSTER +-- +CREATE TABLE clstr_tst_s (rf_a SERIAL PRIMARY KEY, + b INT) DISTRIBUTED BY (rf_a); +CREATE TABLE clstr_tst (a SERIAL PRIMARY KEY, + b INT, + c TEXT, + d TEXT, + CONSTRAINT clstr_tst_con FOREIGN KEY (b) REFERENCES clstr_tst_s) + DISTRIBUTED BY (a); +WARNING: referential integrity (FOREIGN KEY) constraints are not supported in Apache Cloudberry, will not be enforced +CREATE INDEX clstr_tst_b ON clstr_tst (b); +CREATE INDEX clstr_tst_c ON clstr_tst (c); +CREATE INDEX clstr_tst_c_b ON clstr_tst (c,b); +CREATE INDEX clstr_tst_b_c ON clstr_tst (b,c); +INSERT INTO clstr_tst_s (b) VALUES (0); +INSERT INTO clstr_tst_s (b) SELECT b FROM clstr_tst_s; +INSERT INTO clstr_tst_s (b) SELECT b FROM clstr_tst_s; +INSERT INTO clstr_tst_s (b) SELECT b FROM clstr_tst_s; +INSERT INTO clstr_tst_s (b) SELECT b FROM clstr_tst_s; +INSERT INTO clstr_tst_s (b) SELECT b FROM clstr_tst_s; +CREATE TABLE clstr_tst_inh () INHERITS (clstr_tst); +NOTICE: table has parent, setting distribution columns to match parent table +INSERT INTO clstr_tst (b, c) VALUES (11, 'once'); +INSERT INTO clstr_tst (b, c) VALUES (10, 'diez'); +INSERT INTO clstr_tst (b, c) VALUES (31, 'treinta y uno'); +INSERT INTO clstr_tst (b, c) VALUES (22, 'veintidos'); +INSERT INTO clstr_tst (b, c) VALUES (3, 'tres'); +INSERT INTO clstr_tst (b, c) VALUES (20, 'veinte'); +INSERT INTO clstr_tst (b, c) VALUES (23, 'veintitres'); +INSERT INTO clstr_tst (b, c) VALUES (21, 'veintiuno'); +INSERT INTO clstr_tst (b, c) VALUES (4, 'cuatro'); +INSERT INTO clstr_tst (b, c) VALUES (14, 'catorce'); +INSERT INTO clstr_tst (b, c) VALUES (2, 'dos'); +INSERT INTO clstr_tst (b, c) VALUES (18, 'dieciocho'); +INSERT INTO clstr_tst (b, c) VALUES (27, 'veintisiete'); +INSERT INTO clstr_tst (b, c) VALUES (25, 'veinticinco'); +INSERT INTO clstr_tst (b, c) VALUES (13, 'trece'); +INSERT INTO clstr_tst (b, c) VALUES (28, 'veintiocho'); +INSERT INTO clstr_tst (b, c) VALUES (32, 'treinta y dos'); +INSERT INTO clstr_tst (b, c) VALUES (5, 'cinco'); +INSERT INTO clstr_tst (b, c) VALUES (29, 'veintinueve'); +INSERT INTO clstr_tst (b, c) VALUES (1, 'uno'); +INSERT INTO clstr_tst (b, c) VALUES (24, 'veinticuatro'); +INSERT INTO clstr_tst (b, c) VALUES (30, 'treinta'); +INSERT INTO clstr_tst (b, c) VALUES (12, 'doce'); +INSERT INTO clstr_tst (b, c) VALUES (17, 'diecisiete'); +INSERT INTO clstr_tst (b, c) VALUES (9, 'nueve'); +INSERT INTO clstr_tst (b, c) VALUES (19, 'diecinueve'); +INSERT INTO clstr_tst (b, c) VALUES (26, 'veintiseis'); +INSERT INTO clstr_tst (b, c) VALUES (15, 'quince'); +INSERT INTO clstr_tst (b, c) VALUES (7, 'siete'); +INSERT INTO clstr_tst (b, c) VALUES (16, 'dieciseis'); +INSERT INTO clstr_tst (b, c) VALUES (8, 'ocho'); +-- This entry is needed to test that TOASTED values are copied correctly. +INSERT INTO clstr_tst (b, c, d) VALUES (6, 'seis', repeat('xyzzy', 100000)); +CLUSTER clstr_tst_c ON clstr_tst; +SELECT a,b,c,substring(d for 30), length(d) from clstr_tst; + a | b | c | substring | length +----+----+---------------+--------------------------------+-------- + 26 | 19 | diecinueve | | + 12 | 18 | dieciocho | | + 30 | 16 | dieciseis | | + 23 | 12 | doce | | + 31 | 8 | ocho | | + 1 | 11 | once | | + 15 | 13 | trece | | + 20 | 1 | uno | | + 18 | 5 | cinco | | + 24 | 17 | diecisiete | | + 2 | 10 | diez | | + 29 | 7 | siete | | + 22 | 30 | treinta | | + 3 | 31 | treinta y uno | | + 4 | 22 | veintidos | | + 19 | 29 | veintinueve | | + 16 | 28 | veintiocho | | + 27 | 26 | veintiseis | | + 7 | 23 | veintitres | | + 8 | 21 | veintiuno | | + 10 | 14 | catorce | | + 9 | 4 | cuatro | | + 11 | 2 | dos | | + 25 | 9 | nueve | | + 28 | 15 | quince | | + 32 | 6 | seis | xyzzyxyzzyxyzzyxyzzyxyzzyxyzzy | 500000 + 17 | 32 | treinta y dos | | + 5 | 3 | tres | | + 6 | 20 | veinte | | + 14 | 25 | veinticinco | | + 21 | 24 | veinticuatro | | + 13 | 27 | veintisiete | | +(32 rows) + +SELECT a,b,c,substring(d for 30), length(d) from clstr_tst ORDER BY a; + a | b | c | substring | length +----+----+---------------+--------------------------------+-------- + 1 | 11 | once | | + 2 | 10 | diez | | + 3 | 31 | treinta y uno | | + 4 | 22 | veintidos | | + 5 | 3 | tres | | + 6 | 20 | veinte | | + 7 | 23 | veintitres | | + 8 | 21 | veintiuno | | + 9 | 4 | cuatro | | + 10 | 14 | catorce | | + 11 | 2 | dos | | + 12 | 18 | dieciocho | | + 13 | 27 | veintisiete | | + 14 | 25 | veinticinco | | + 15 | 13 | trece | | + 16 | 28 | veintiocho | | + 17 | 32 | treinta y dos | | + 18 | 5 | cinco | | + 19 | 29 | veintinueve | | + 20 | 1 | uno | | + 21 | 24 | veinticuatro | | + 22 | 30 | treinta | | + 23 | 12 | doce | | + 24 | 17 | diecisiete | | + 25 | 9 | nueve | | + 26 | 19 | diecinueve | | + 27 | 26 | veintiseis | | + 28 | 15 | quince | | + 29 | 7 | siete | | + 30 | 16 | dieciseis | | + 31 | 8 | ocho | | + 32 | 6 | seis | xyzzyxyzzyxyzzyxyzzyxyzzyxyzzy | 500000 +(32 rows) + +SELECT a,b,c,substring(d for 30), length(d) from clstr_tst ORDER BY b; + a | b | c | substring | length +----+----+---------------+--------------------------------+-------- + 20 | 1 | uno | | + 11 | 2 | dos | | + 5 | 3 | tres | | + 9 | 4 | cuatro | | + 18 | 5 | cinco | | + 32 | 6 | seis | xyzzyxyzzyxyzzyxyzzyxyzzyxyzzy | 500000 + 29 | 7 | siete | | + 31 | 8 | ocho | | + 25 | 9 | nueve | | + 2 | 10 | diez | | + 1 | 11 | once | | + 23 | 12 | doce | | + 15 | 13 | trece | | + 10 | 14 | catorce | | + 28 | 15 | quince | | + 30 | 16 | dieciseis | | + 24 | 17 | diecisiete | | + 12 | 18 | dieciocho | | + 26 | 19 | diecinueve | | + 6 | 20 | veinte | | + 8 | 21 | veintiuno | | + 4 | 22 | veintidos | | + 7 | 23 | veintitres | | + 21 | 24 | veinticuatro | | + 14 | 25 | veinticinco | | + 27 | 26 | veintiseis | | + 13 | 27 | veintisiete | | + 16 | 28 | veintiocho | | + 19 | 29 | veintinueve | | + 22 | 30 | treinta | | + 3 | 31 | treinta y uno | | + 17 | 32 | treinta y dos | | +(32 rows) + +SELECT a,b,c,substring(d for 30), length(d) from clstr_tst ORDER BY c; + a | b | c | substring | length +----+----+---------------+--------------------------------+-------- + 10 | 14 | catorce | | + 18 | 5 | cinco | | + 9 | 4 | cuatro | | + 26 | 19 | diecinueve | | + 12 | 18 | dieciocho | | + 30 | 16 | dieciseis | | + 24 | 17 | diecisiete | | + 2 | 10 | diez | | + 23 | 12 | doce | | + 11 | 2 | dos | | + 25 | 9 | nueve | | + 31 | 8 | ocho | | + 1 | 11 | once | | + 28 | 15 | quince | | + 32 | 6 | seis | xyzzyxyzzyxyzzyxyzzyxyzzyxyzzy | 500000 + 29 | 7 | siete | | + 15 | 13 | trece | | + 22 | 30 | treinta | | + 17 | 32 | treinta y dos | | + 3 | 31 | treinta y uno | | + 5 | 3 | tres | | + 20 | 1 | uno | | + 6 | 20 | veinte | | + 14 | 25 | veinticinco | | + 21 | 24 | veinticuatro | | + 4 | 22 | veintidos | | + 19 | 29 | veintinueve | | + 16 | 28 | veintiocho | | + 27 | 26 | veintiseis | | + 13 | 27 | veintisiete | | + 7 | 23 | veintitres | | + 8 | 21 | veintiuno | | +(32 rows) + +-- Verify that inheritance link still works +INSERT INTO clstr_tst_inh VALUES (0, 100, 'in child table'); +SELECT a,b,c,substring(d for 30), length(d) from clstr_tst; + a | b | c | substring | length +----+-----+----------------+--------------------------------+-------- + 18 | 5 | cinco | | + 24 | 17 | diecisiete | | + 2 | 10 | diez | | + 29 | 7 | siete | | + 22 | 30 | treinta | | + 3 | 31 | treinta y uno | | + 4 | 22 | veintidos | | + 19 | 29 | veintinueve | | + 16 | 28 | veintiocho | | + 27 | 26 | veintiseis | | + 7 | 23 | veintitres | | + 8 | 21 | veintiuno | | + 26 | 19 | diecinueve | | + 12 | 18 | dieciocho | | + 30 | 16 | dieciseis | | + 23 | 12 | doce | | + 31 | 8 | ocho | | + 1 | 11 | once | | + 15 | 13 | trece | | + 20 | 1 | uno | | + 0 | 100 | in child table | | + 10 | 14 | catorce | | + 9 | 4 | cuatro | | + 11 | 2 | dos | | + 25 | 9 | nueve | | + 28 | 15 | quince | | + 32 | 6 | seis | xyzzyxyzzyxyzzyxyzzyxyzzyxyzzy | 500000 + 17 | 32 | treinta y dos | | + 5 | 3 | tres | | + 6 | 20 | veinte | | + 14 | 25 | veinticinco | | + 21 | 24 | veinticuatro | | + 13 | 27 | veintisiete | | +(33 rows) + +-- Verify that foreign key link still works +INSERT INTO clstr_tst (b, c) VALUES (1111, 'this should fail'); +SELECT conname FROM pg_constraint WHERE conrelid = 'clstr_tst'::regclass +ORDER BY 1; + conname +---------------- + clstr_tst_con + clstr_tst_pkey +(2 rows) + +SELECT relname, relkind, + EXISTS(SELECT 1 FROM pg_class WHERE oid = c.reltoastrelid) AS hastoast +FROM pg_class c WHERE relname LIKE 'clstr_tst%' ORDER BY relname; + relname | relkind | hastoast +----------------------+---------+---------- + clstr_tst | r | t + clstr_tst_a_seq | S | f + clstr_tst_b | i | f + clstr_tst_b_c | i | f + clstr_tst_c | i | f + clstr_tst_c_b | i | f + clstr_tst_inh | r | t + clstr_tst_pkey | i | f + clstr_tst_s | r | f + clstr_tst_s_pkey | i | f + clstr_tst_s_rf_a_seq | S | f +(11 rows) + +-- Verify that indisclustered is correctly set +SELECT pg_class.relname FROM pg_index, pg_class, pg_class AS pg_class_2 +WHERE pg_class.oid=indexrelid + AND indrelid=pg_class_2.oid + AND pg_class_2.relname = 'clstr_tst' + AND indisclustered; + relname +------------- + clstr_tst_c +(1 row) + +-- Try changing indisclustered +ALTER TABLE clstr_tst CLUSTER ON clstr_tst_b_c; +SELECT pg_class.relname FROM pg_index, pg_class, pg_class AS pg_class_2 +WHERE pg_class.oid=indexrelid + AND indrelid=pg_class_2.oid + AND pg_class_2.relname = 'clstr_tst' + AND indisclustered; + relname +--------------- + clstr_tst_b_c +(1 row) + +-- Try turning off all clustering +ALTER TABLE clstr_tst SET WITHOUT CLUSTER; +SELECT pg_class.relname FROM pg_index, pg_class, pg_class AS pg_class_2 +WHERE pg_class.oid=indexrelid + AND indrelid=pg_class_2.oid + AND pg_class_2.relname = 'clstr_tst' + AND indisclustered; + relname +--------- +(0 rows) + +-- Verify that clustering all tables does in fact cluster the right ones +CREATE USER regress_clstr_user; +NOTICE: resource queue required -- using default resource queue "pg_default" +CREATE TABLE clstr_1 (a INT PRIMARY KEY); +CREATE TABLE clstr_2 (a INT PRIMARY KEY); +CREATE TABLE clstr_3 (a INT PRIMARY KEY); +ALTER TABLE clstr_1 OWNER TO regress_clstr_user; +ALTER TABLE clstr_3 OWNER TO regress_clstr_user; +GRANT SELECT ON clstr_2 TO regress_clstr_user; +INSERT INTO clstr_1 VALUES (2); +INSERT INTO clstr_1 VALUES (1); +INSERT INTO clstr_2 VALUES (2); +INSERT INTO clstr_2 VALUES (1); +INSERT INTO clstr_3 VALUES (2); +INSERT INTO clstr_3 VALUES (1); +-- "CLUSTER " on a table that hasn't been clustered +CLUSTER clstr_2; +ERROR: there is no previously clustered index for table "clstr_2" +CLUSTER clstr_1_pkey ON clstr_1; +CLUSTER clstr_2 USING clstr_2_pkey; +SELECT * FROM clstr_1 UNION ALL + SELECT * FROM clstr_2 UNION ALL + SELECT * FROM clstr_3; + a +--- + 2 + 2 + 2 + 1 + 1 + 1 +(6 rows) + +-- revert to the original state +DELETE FROM clstr_1; +DELETE FROM clstr_2; +DELETE FROM clstr_3; +INSERT INTO clstr_1 VALUES (2); +INSERT INTO clstr_1 VALUES (1); +INSERT INTO clstr_2 VALUES (2); +INSERT INTO clstr_2 VALUES (1); +INSERT INTO clstr_3 VALUES (2); +INSERT INTO clstr_3 VALUES (1); +-- this user can only cluster clstr_1 and clstr_3, but the latter +-- has not been clustered +SET SESSION AUTHORIZATION regress_clstr_user; +CLUSTER; +SELECT * FROM clstr_1 UNION ALL + SELECT * FROM clstr_2 UNION ALL + SELECT * FROM clstr_3; + a +--- + 1 + 1 + 1 + 2 + 2 + 2 +(6 rows) + +-- cluster a single table using the indisclustered bit previously set +DELETE FROM clstr_1; +INSERT INTO clstr_1 VALUES (2); +INSERT INTO clstr_1 VALUES (1); +CLUSTER clstr_1; +SELECT * FROM clstr_1; + a +--- + 2 + 1 +(2 rows) + +-- Test MVCC-safety of cluster. There isn't much we can do to verify the +-- results with a single backend... +CREATE TABLE clustertest (key int, distkey int) DISTRIBUTED BY (distkey); +CREATE INDEX clustertest_pkey ON clustertest (key); +INSERT INTO clustertest VALUES (10, 1); +INSERT INTO clustertest VALUES (20, 2); +INSERT INTO clustertest VALUES (30, 1); +INSERT INTO clustertest VALUES (40, 2); +INSERT INTO clustertest VALUES (50, 3); +-- Use a transaction so that updates are not committed when CLUSTER sees 'em +BEGIN; +-- Test update where the old row version is found first in the scan +UPDATE clustertest SET key = 100 WHERE key = 10; +-- Test update where the new row version is found first in the scan +UPDATE clustertest SET key = 35 WHERE key = 40; +-- Test longer update chain +UPDATE clustertest SET key = 60 WHERE key = 50; +UPDATE clustertest SET key = 70 WHERE key = 60; +UPDATE clustertest SET key = 80 WHERE key = 70; +SELECT key FROM clustertest; + key +----- + 20 + 35 + 80 + 30 + 100 +(5 rows) + +CLUSTER clustertest_pkey ON clustertest; +SELECT key FROM clustertest; + key +----- + 20 + 35 + 80 + 30 + 100 +(5 rows) + +COMMIT; +SELECT key FROM clustertest; + key +----- + 20 + 35 + 80 + 30 + 100 +(5 rows) + +-- check that temp tables can be clustered +create temp table clstr_temp (col1 int primary key, col2 text); +insert into clstr_temp values (2, 'two'), (1, 'one'); +cluster clstr_temp using clstr_temp_pkey; +select * from clstr_temp; + col1 | col2 +------+------ + 2 | two + 1 | one +(2 rows) + +drop table clstr_temp; +RESET SESSION AUTHORIZATION; +-- check clustering an empty table +DROP TABLE clustertest; +CREATE TABLE clustertest (f1 int PRIMARY KEY); +CLUSTER clustertest USING clustertest_pkey; +CLUSTER clustertest; +-- Check that partitioned tables cannot be clustered +CREATE TABLE clstrpart (a int) PARTITION BY RANGE (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. +CREATE INDEX clstrpart_idx ON clstrpart (a); +ALTER TABLE clstrpart CLUSTER ON clstrpart_idx; +ERROR: cannot mark index clustered in partitioned table +CLUSTER clstrpart USING clstrpart_idx; +ERROR: cannot cluster a partitioned table +DROP TABLE clstrpart; +-- Test CLUSTER with external tuplesorting +-- The tests assume that the rows come out in the physical order, as +-- sorted by CLUSTER. In GPDB, add a dummy column to force all the rows to go +-- to the same segment, otherwise the rows come out in random order from the +-- segments. +create table clstr_4 as select 1 as dummy, * from tenk1 distributed by (dummy); +create index cluster_sort on clstr_4 (hundred, thousand, tenthous); +-- ensure we don't use the index in CLUSTER nor the checking SELECTs +set enable_indexscan = off; +-- Use external sort: +set maintenance_work_mem = '1MB'; +cluster clstr_4 using cluster_sort; +select * from +(select hundred, lag(hundred) over (order by hundred) as lhundred, + thousand, lag(thousand) over (order by hundred) as lthousand, + tenthous, lag(tenthous) over (order by hundred) as ltenthous from clstr_4) ss +where row(hundred, thousand, tenthous) <= row(lhundred, lthousand, ltenthous); + hundred | lhundred | thousand | lthousand | tenthous | ltenthous +---------+----------+----------+-----------+----------+----------- +(0 rows) + +reset enable_indexscan; +reset maintenance_work_mem; +-- test CLUSTER on expression index +CREATE TABLE clstr_expression(id serial primary key, a int, b text COLLATE "C"); +INSERT INTO clstr_expression(a, b) SELECT g.i % 42, 'prefix'||g.i FROM generate_series(1, 133) g(i); +CREATE INDEX clstr_expression_minus_a ON clstr_expression ((-a), b); +CREATE INDEX clstr_expression_upper_b ON clstr_expression ((upper(b))); +-- enable to keep same plan with pg +SET gp_enable_relsize_collection= on; +-- verify indexes work before cluster +BEGIN; +SET LOCAL enable_seqscan = false; +EXPLAIN (COSTS OFF) SELECT * FROM clstr_expression WHERE upper(b) = 'PREFIX3'; +NOTICE: One or more columns in the following table(s) do not have statistics: clstr_expression +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. + QUERY PLAN +---------------------------------------------- + Gather Motion 3:1 (slice1; segments: 3) + -> Seq Scan on clstr_expression + Filter: (upper(b) = 'PREFIX3'::text) + Optimizer: GPORCA +(4 rows) + +SELECT * FROM clstr_expression WHERE upper(b) = 'PREFIX3'; +NOTICE: One or more columns in the following table(s) do not have statistics: clstr_expression +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. + id | a | b +----+---+--------- + 3 | 3 | prefix3 +(1 row) + +EXPLAIN (COSTS OFF) SELECT * FROM clstr_expression WHERE -a = -3 ORDER BY -a, b; +NOTICE: One or more columns in the following table(s) do not have statistics: clstr_expression +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. + QUERY PLAN +----------------------------------------------------- + Result + -> Gather Motion 3:1 (slice1; segments: 3) + Merge Key: ((- a)), b + -> Sort + Sort Key: ((- a)), b COLLATE "C" + -> Seq Scan on clstr_expression + Filter: ((- a) = '-3'::integer) + Optimizer: GPORCA +(8 rows) + +SELECT * FROM clstr_expression WHERE -a = -3 ORDER BY -a, b; +NOTICE: One or more columns in the following table(s) do not have statistics: clstr_expression +HINT: For non-partitioned tables, run analyze (). For partitioned tables, run analyze rootpartition (). See log for columns missing statistics. + id | a | b +-----+---+----------- + 129 | 3 | prefix129 + 3 | 3 | prefix3 + 45 | 3 | prefix45 + 87 | 3 | prefix87 +(4 rows) + +COMMIT; +-- and after clustering on clstr_expression_minus_a +CLUSTER clstr_expression USING clstr_expression_minus_a; +WITH rows AS + (SELECT ctid, lag(a) OVER (PARTITION BY gp_segment_id ORDER BY ctid) AS la, a FROM clstr_expression) +SELECT * FROM rows WHERE la < a; + ctid | la | a +------+----+--- +(0 rows) + +BEGIN; +SET LOCAL enable_seqscan = false; +EXPLAIN (COSTS OFF) SELECT * FROM clstr_expression WHERE upper(b) = 'PREFIX3'; + QUERY PLAN +---------------------------------------------- + Gather Motion 3:1 (slice1; segments: 3) + -> Seq Scan on clstr_expression + Filter: (upper(b) = 'PREFIX3'::text) + Optimizer: GPORCA +(4 rows) + +SELECT * FROM clstr_expression WHERE upper(b) = 'PREFIX3'; + id | a | b +----+---+--------- + 3 | 3 | prefix3 +(1 row) + +EXPLAIN (COSTS OFF) SELECT * FROM clstr_expression WHERE -a = -3 ORDER BY -a, b; + QUERY PLAN +------------------------------------------------------------ + Result + -> Sort + Sort Key: ((- a)), b COLLATE "C" + -> Result + -> Gather Motion 3:1 (slice1; segments: 3) + -> Seq Scan on clstr_expression + Filter: ((- a) = '-3'::integer) + Optimizer: GPORCA +(8 rows) + +SELECT * FROM clstr_expression WHERE -a = -3 ORDER BY -a, b; + id | a | b +-----+---+----------- + 129 | 3 | prefix129 + 3 | 3 | prefix3 + 45 | 3 | prefix45 + 87 | 3 | prefix87 +(4 rows) + +COMMIT; +-- and after clustering on clstr_expression_upper_b +CLUSTER clstr_expression USING clstr_expression_upper_b; +WITH rows AS + (SELECT ctid, lag(b) OVER (PARTITION BY gp_segment_id ORDER BY ctid) AS lb, b FROM clstr_expression) +SELECT * FROM rows WHERE upper(lb) > upper(b); + ctid | lb | b +------+----+--- +(0 rows) + +BEGIN; +SET LOCAL enable_seqscan = false; +EXPLAIN (COSTS OFF) SELECT * FROM clstr_expression WHERE upper(b) = 'PREFIX3'; + QUERY PLAN +---------------------------------------------- + Gather Motion 3:1 (slice1; segments: 3) + -> Seq Scan on clstr_expression + Filter: (upper(b) = 'PREFIX3'::text) + Optimizer: GPORCA +(4 rows) + +SELECT * FROM clstr_expression WHERE upper(b) = 'PREFIX3'; + id | a | b +----+---+--------- + 3 | 3 | prefix3 +(1 row) + +EXPLAIN (COSTS OFF) SELECT * FROM clstr_expression WHERE -a = -3 ORDER BY -a, b; + QUERY PLAN +------------------------------------------------------------ + Result + -> Sort + Sort Key: ((- a)), b COLLATE "C" + -> Result + -> Gather Motion 3:1 (slice1; segments: 3) + -> Seq Scan on clstr_expression + Filter: ((- a) = '-3'::integer) + Optimizer: GPORCA +(8 rows) + +SELECT * FROM clstr_expression WHERE -a = -3 ORDER BY -a, b; + id | a | b +-----+---+----------- + 129 | 3 | prefix129 + 3 | 3 | prefix3 + 45 | 3 | prefix45 + 87 | 3 | prefix87 +(4 rows) + +COMMIT; +-- clean up +SET gp_enable_relsize_collection= off; +DROP TABLE clustertest; +DROP TABLE clstr_1; +DROP TABLE clstr_2; +DROP TABLE clstr_3; +DROP TABLE clstr_4; +DROP TABLE clstr_expression; +DROP USER regress_clstr_user; +-- Test transactional safety of CLUSTER against heap +CREATE TABLE foo (a int, b varchar, c int) DISTRIBUTED BY (a); +INSERT INTO foo SELECT i, 'initial insert' || i, i FROM generate_series(1,10000)i; +CREATE index ifoo on foo using btree (b); +-- execute cluster in a transaction but don't commit the transaction +BEGIN; +CLUSTER foo USING ifoo; +ABORT; +-- try cluster again +CLUSTER foo USING ifoo; +DROP TABLE foo; diff --git a/src/test/regress/expected/collation_orca.out b/src/test/regress/expected/collation_orca.out new file mode 100644 index 00000000000..76f13e43ddc --- /dev/null +++ b/src/test/regress/expected/collation_orca.out @@ -0,0 +1,980 @@ +-- +-- Test ORCA optimizer handling of COLLATE "C" in en_US.UTF-8 database. +-- +-- This test verifies that ORCA correctly propagates column-level and +-- expression-level collation through its internal DXL representation, +-- producing plans with correct collation semantics. +-- +-- Prerequisites: database must have LC_COLLATE=en_US.UTF-8 (non-C locale) +-- so that C collation differs from the default. +-- +-- Force ORCA and make fallback visible +SET optimizer TO on; +SET optimizer_trace_fallback TO on; +CREATE SCHEMA collate_orca; +SET search_path = collate_orca; +-- ====================================================================== +-- Setup: tables with COLLATE "C" columns +-- ====================================================================== +CREATE TABLE t_c_collation ( + id int, + name text COLLATE "C", + val varchar(50) COLLATE "C" +) DISTRIBUTED BY (id); +CREATE TABLE t_default_collation ( + id int, + name text, + val varchar(50) +) DISTRIBUTED BY (id); +-- Mixed: some columns C, some default +CREATE TABLE t_mixed_collation ( + id int, + c_name text COLLATE "C", + d_name text +) DISTRIBUTED BY (id); +-- Insert test data: uppercase letters have lower byte values than lowercase in ASCII +-- C collation: 'ABC' < 'abc' (byte order) +-- en_US.UTF-8: 'abc' < 'ABC' (case-insensitive primary sort) +INSERT INTO t_c_collation VALUES + (1, 'abc', 'apple'), + (2, 'ABC', 'APPLE'), + (3, 'def', 'banana'), + (4, 'DEF', 'BANANA'), + (5, 'ghi', 'cherry'), + (6, 'GHI', 'CHERRY'); +INSERT INTO t_default_collation SELECT * FROM t_c_collation; +INSERT INTO t_mixed_collation SELECT id, name, name FROM t_c_collation; +ANALYZE t_c_collation; +ANALYZE t_default_collation; +ANALYZE t_mixed_collation; +-- ====================================================================== +-- Test 8.3: ORDER BY uses C collation, not en_US.UTF-8 +-- ====================================================================== +-- C collation: uppercase before lowercase (byte order: A=65 < a=97) +-- If ORCA incorrectly uses default collation, order will be different +SELECT name FROM t_c_collation ORDER BY name; + name +------ + ABC + DEF + GHI + abc + def + ghi +(6 rows) + +-- Compare with default collation table (should have different order) +SELECT name FROM t_default_collation ORDER BY name; + name +------ + abc + ABC + def + DEF + ghi + GHI +(6 rows) + +-- Verify sort order is strictly byte-based +SELECT name, val FROM t_c_collation ORDER BY val; + name | val +------+-------- + ABC | APPLE + DEF | BANANA + GHI | CHERRY + abc | apple + def | banana + ghi | cherry +(6 rows) + +-- ORDER BY DESC +SELECT name FROM t_c_collation ORDER BY name DESC; + name +------ + ghi + def + abc + GHI + DEF + ABC +(6 rows) + +-- Multi-column ORDER BY with C collation +SELECT name, val FROM t_c_collation ORDER BY name, val; + name | val +------+-------- + ABC | APPLE + DEF | BANANA + GHI | CHERRY + abc | apple + def | banana + ghi | cherry +(6 rows) + +-- ====================================================================== +-- Test 8.4: WHERE = comparison uses C collation +-- ====================================================================== +-- Equality: case-sensitive under C collation +SELECT id, name FROM t_c_collation WHERE name = 'abc'; + id | name +----+------ + 1 | abc +(1 row) + +SELECT id, name FROM t_c_collation WHERE name = 'ABC'; + id | name +----+------ + 2 | ABC +(1 row) + +-- These should return different rows (C is case-sensitive) +SELECT count(*) FROM t_c_collation WHERE name = 'abc'; + count +------- + 1 +(1 row) + +SELECT count(*) FROM t_c_collation WHERE name = 'ABC'; + count +------- + 1 +(1 row) + +-- Range comparison: under C, 'Z' < 'a' (byte order) +SELECT name FROM t_c_collation WHERE name < 'a' ORDER BY name; + name +------ + ABC + DEF + GHI +(3 rows) + +SELECT name FROM t_c_collation WHERE name >= 'a' ORDER BY name; + name +------ + abc + def + ghi +(3 rows) + +-- IN list with C collation +SELECT name FROM t_c_collation WHERE name IN ('abc', 'DEF') ORDER BY name; + name +------ + DEF + abc +(2 rows) + +-- ====================================================================== +-- Test 8.5: JOIN on COLLATE "C" columns +-- ====================================================================== +-- Inner join: should match on exact case under C collation +SELECT a.id, a.name, b.id, b.name +FROM t_c_collation a JOIN t_c_collation b ON a.name = b.name +WHERE a.id < b.id +ORDER BY a.name, a.id; + id | name | id | name +----+------+----+------ +(0 rows) + +-- Join between C-collation and default-collation tables +-- The join should still work (both sides produce same text) +SELECT c.id, c.name, d.id, d.name +FROM t_c_collation c JOIN t_default_collation d ON c.id = d.id +WHERE c.name = d.name +ORDER BY c.name; + id | name | id | name +----+------+----+------ + 2 | ABC | 2 | ABC + 4 | DEF | 4 | DEF + 6 | GHI | 6 | GHI + 1 | abc | 1 | abc + 3 | def | 3 | def + 5 | ghi | 5 | ghi +(6 rows) + +-- Self-join with inequality (tests collation in merge/hash join) +SELECT a.name, b.name +FROM t_c_collation a JOIN t_c_collation b ON a.name < b.name +WHERE a.id = 1 AND b.id IN (2, 4, 6) +ORDER BY a.name, b.name; + name | name +------+------ +(0 rows) + +-- ====================================================================== +-- Test 8.6: GROUP BY on COLLATE "C" column +-- ====================================================================== +-- Under C collation, 'abc' and 'ABC' are different groups +SELECT name, count(*) FROM t_c_collation GROUP BY name ORDER BY name; + name | count +------+------- + ABC | 1 + DEF | 1 + GHI | 1 + abc | 1 + def | 1 + ghi | 1 +(6 rows) + +-- Aggregate with C collation grouping +SELECT name, min(val), max(val) +FROM t_c_collation GROUP BY name ORDER BY name; + name | min | max +------+--------+-------- + ABC | APPLE | APPLE + DEF | BANANA | BANANA + GHI | CHERRY | CHERRY + abc | apple | apple + def | banana | banana + ghi | cherry | cherry +(6 rows) + +-- GROUP BY on expression involving C collation column +SELECT upper(name) as uname, count(*) +FROM t_c_collation GROUP BY upper(name) ORDER BY uname; + uname | count +-------+------- + ABC | 2 + DEF | 2 + GHI | 2 +(3 rows) + +-- HAVING with C collation +SELECT name, count(*) as cnt +FROM t_c_collation GROUP BY name HAVING name > 'Z' ORDER BY name; + name | cnt +------+----- + abc | 1 + def | 1 + ghi | 1 +(3 rows) + +-- ====================================================================== +-- Test 8.7: Window functions with COLLATE "C" PARTITION BY +-- ====================================================================== +-- Partition by C-collation column +SELECT name, val, + row_number() OVER (PARTITION BY name ORDER BY val) as rn +FROM t_c_collation +ORDER BY name, val; + name | val | rn +------+--------+---- + ABC | APPLE | 1 + DEF | BANANA | 1 + GHI | CHERRY | 1 + abc | apple | 1 + def | banana | 1 + ghi | cherry | 1 +(6 rows) + +-- Window with ORDER BY on C-collation column +SELECT name, val, + rank() OVER (ORDER BY name) as rnk +FROM t_c_collation +ORDER BY name, val; + name | val | rnk +------+--------+----- + ABC | APPLE | 1 + DEF | BANANA | 2 + GHI | CHERRY | 3 + abc | apple | 4 + def | banana | 5 + ghi | cherry | 6 +(6 rows) + +-- Multiple window functions +SELECT name, val, + count(*) OVER (PARTITION BY name) as grp_cnt, + first_value(val) OVER (PARTITION BY name ORDER BY val) as first_val +FROM t_c_collation +ORDER BY name, val; + name | val | grp_cnt | first_val +------+--------+---------+----------- + ABC | APPLE | 1 | APPLE + DEF | BANANA | 1 | BANANA + GHI | CHERRY | 1 | CHERRY + abc | apple | 1 | apple + def | banana | 1 | banana + ghi | cherry | 1 | cherry +(6 rows) + +-- Window with expression-level COLLATE "C" on default-collation table +SELECT name, val, + row_number() OVER (PARTITION BY name COLLATE "C" ORDER BY val COLLATE "C") as rn +FROM t_default_collation +ORDER BY name COLLATE "C", val COLLATE "C"; +INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner +DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation + name | val | rn +------+--------+---- + ABC | APPLE | 1 + DEF | BANANA | 1 + GHI | CHERRY | 1 + abc | apple | 1 + def | banana | 1 + ghi | cherry | 1 +(6 rows) + +-- ====================================================================== +-- Test 8.8: EXPLAIN shows correct collation in plan +-- ====================================================================== +-- The sort key should reflect C collation, not default +EXPLAIN (COSTS OFF) SELECT name FROM t_c_collation ORDER BY name; + QUERY PLAN +------------------------------------------ + Gather Motion 3:1 (slice1; segments: 3) + Merge Key: name + -> Sort + Sort Key: name COLLATE "C" + -> Seq Scan on t_c_collation + Optimizer: GPORCA +(6 rows) + +-- Join plan should use correct collation +EXPLAIN (COSTS OFF) +SELECT a.name, b.name +FROM t_c_collation a JOIN t_c_collation b ON a.name = b.name +ORDER BY a.name; + QUERY PLAN +------------------------------------------------------------------------ + Gather Motion 3:1 (slice1; segments: 3) + Merge Key: a.name + -> Sort + Sort Key: a.name COLLATE "C" + -> Hash Join + Hash Cond: (a.name = b.name) + -> Redistribute Motion 3:3 (slice2; segments: 3) + Hash Key: a.name + -> Seq Scan on t_c_collation a + -> Hash + -> Redistribute Motion 3:3 (slice3; segments: 3) + Hash Key: b.name + -> Seq Scan on t_c_collation b + Optimizer: GPORCA +(14 rows) + +-- Aggregate plan +EXPLAIN (COSTS OFF) +SELECT name, count(*) FROM t_c_collation GROUP BY name ORDER BY name; + QUERY PLAN +------------------------------------------------------------------ + Gather Motion 3:1 (slice1; segments: 3) + Merge Key: name + -> GroupAggregate + Group Key: name + -> Sort + Sort Key: name COLLATE "C" + -> Redistribute Motion 3:3 (slice2; segments: 3) + Hash Key: name + -> Seq Scan on t_c_collation + Optimizer: GPORCA +(10 rows) + +-- ====================================================================== +-- Test 8.9: Mixed default + C collation columns in same query +-- ====================================================================== +-- Query referencing both C and default collation columns +SELECT c_name, d_name +FROM t_mixed_collation +ORDER BY c_name; + c_name | d_name +--------+-------- + ABC | ABC + DEF | DEF + GHI | GHI + abc | abc + def | def + ghi | ghi +(6 rows) + +SELECT c_name, d_name +FROM t_mixed_collation +ORDER BY d_name; + c_name | d_name +--------+-------- + abc | abc + ABC | ABC + def | def + DEF | DEF + ghi | ghi + GHI | GHI +(6 rows) + +-- Mixed columns in WHERE +SELECT id, c_name, d_name +FROM t_mixed_collation +WHERE c_name = 'abc' AND d_name = 'abc'; + id | c_name | d_name +----+--------+-------- + 1 | abc | abc +(1 row) + +-- Mixed columns in GROUP BY +SELECT c_name, d_name, count(*) +FROM t_mixed_collation +GROUP BY c_name, d_name +ORDER BY c_name, d_name; + c_name | d_name | count +--------+--------+------- + ABC | ABC | 1 + DEF | DEF | 1 + GHI | GHI | 1 + abc | abc | 1 + def | def | 1 + ghi | ghi | 1 +(6 rows) + +-- Join on C column, filter on default column +SELECT m.id, m.c_name, m.d_name +FROM t_mixed_collation m JOIN t_c_collation c ON m.c_name = c.name +WHERE m.d_name > 'D' +ORDER BY m.c_name; + id | c_name | d_name +----+--------+-------- + 4 | DEF | DEF + 6 | GHI | GHI + 3 | def | def + 5 | ghi | ghi +(4 rows) + +-- ====================================================================== +-- Test: Collation resolution for mixed-collation argument lists +-- gpdb::ExprCollation(List) must match PG's merge_collation_state() rule: +-- non-default implicit collation always beats default, regardless of +-- argument order. Previously the translator just returned the first +-- non-InvalidOid collation, so (default, C) picked default — wrong. +-- ====================================================================== +-- coalesce: DEFAULT column first, C column second +-- Must sort in C byte order (A < B < a < b), not locale order (a < A < b < B) +SELECT coalesce(d_name, c_name) AS r FROM t_mixed_collation ORDER BY coalesce(d_name, c_name); + r +----- + ABC + DEF + GHI + abc + def + ghi +(6 rows) + +-- coalesce: C column first, DEFAULT column second (control — always worked) +SELECT coalesce(c_name, d_name) AS r FROM t_mixed_collation ORDER BY coalesce(c_name, d_name); + r +----- + ABC + DEF + GHI + abc + def + ghi +(6 rows) + +-- Verify both orders produce identical results +SELECT coalesce(d_name, c_name) AS dc, coalesce(c_name, d_name) AS cd +FROM t_mixed_collation +ORDER BY coalesce(d_name, c_name); + dc | cd +-----+----- + ABC | ABC + DEF | DEF + GHI | GHI + abc | abc + def | def + ghi | ghi +(6 rows) + +-- EXPLAIN must show COLLATE "C" on the sort key regardless of arg order +EXPLAIN (COSTS OFF) +SELECT * FROM t_mixed_collation ORDER BY coalesce(d_name, c_name); + QUERY PLAN +---------------------------------------------------------------- + Result + -> Gather Motion 3:1 (slice1; segments: 3) + Merge Key: (COALESCE(d_name, c_name)) + -> Sort + Sort Key: (COALESCE(d_name, c_name)) COLLATE "C" + -> Seq Scan on t_mixed_collation + Optimizer: GPORCA +(7 rows) + +-- Operator expression: 'literal' || c_col (DEFAULT || C → should pick C) +SELECT d_name || c_name AS r FROM t_mixed_collation ORDER BY d_name || c_name; + r +-------- + ABCABC + DEFDEF + GHIGHI + abcabc + defdef + ghighi +(6 rows) + +-- min/max with mixed-collation coalesce argument +SELECT min(coalesce(d_name, c_name)), max(coalesce(d_name, c_name)) FROM t_mixed_collation; + min | max +-----+----- + ABC | ghi +(1 row) + +-- CASE result with mixed collation branches +-- WHEN branch returns d_name (default), ELSE returns c_name (C). +-- Output collation should be C. +SELECT CASE WHEN id <= 3 THEN d_name ELSE c_name END AS r +FROM t_mixed_collation +ORDER BY CASE WHEN id <= 3 THEN d_name ELSE c_name END; + r +----- + ABC + DEF + GHI + abc + def + ghi +(6 rows) + +-- ====================================================================== +-- Test: Expression-level COLLATE "C" +-- ====================================================================== +-- COLLATE in WHERE clause on default-collation table +SELECT name FROM t_default_collation WHERE name COLLATE "C" < 'a' ORDER BY name COLLATE "C"; +INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner +DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation + name +------ + ABC + DEF + GHI +(3 rows) + +-- COLLATE in ORDER BY on default-collation table +SELECT name FROM t_default_collation ORDER BY name COLLATE "C"; +INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner +DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation + name +------ + ABC + DEF + GHI + abc + def + ghi +(6 rows) + +-- COLLATE in expression +SELECT name, name COLLATE "C" < 'a' as is_upper +FROM t_default_collation ORDER BY name COLLATE "C"; +INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner +DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation + name | is_upper +------+---------- + ABC | t + DEF | t + GHI | t + abc | f + def | f + ghi | f +(6 rows) + +-- ====================================================================== +-- Test: Subqueries and CTEs with C collation +-- ====================================================================== +-- Subquery preserves C collation +SELECT * FROM ( + SELECT name, val FROM t_c_collation ORDER BY name +) sub +ORDER BY name; + name | val +------+-------- + ABC | APPLE + DEF | BANANA + GHI | CHERRY + abc | apple + def | banana + ghi | cherry +(6 rows) + +-- CTE with C collation +WITH ranked AS ( + SELECT name, val, row_number() OVER (ORDER BY name) as rn + FROM t_c_collation +) +SELECT * FROM ranked ORDER BY rn; + name | val | rn +------+--------+---- + abc | apple | 1 + ABC | APPLE | 2 + def | banana | 3 + DEF | BANANA | 4 + ghi | cherry | 5 + GHI | CHERRY | 6 +(6 rows) + +-- Correlated subquery +SELECT c.name, c.val +FROM t_c_collation c +WHERE c.name = (SELECT min(name) FROM t_c_collation WHERE val = c.val) +ORDER BY c.name; + name | val +------+-------- + ABC | APPLE + DEF | BANANA + GHI | CHERRY + abc | apple + def | banana + ghi | cherry +(6 rows) + +-- ====================================================================== +-- Test: UNION / INTERSECT / EXCEPT with C collation +-- ====================================================================== +SELECT name FROM t_c_collation WHERE name < 'a' +UNION ALL +SELECT name FROM t_c_collation WHERE name >= 'a' +ORDER BY name; + name +------ + ABC + DEF + GHI + abc + def + ghi +(6 rows) + +SELECT name FROM t_c_collation +INTERSECT +SELECT name FROM t_default_collation +ORDER BY name; + name +------ + ABC + DEF + GHI + abc + def + ghi +(6 rows) + +-- ====================================================================== +-- Test: DISTINCT C collation column +-- ====================================================================== +-- Under C collation, 'abc' and 'ABC' are distinct +SELECT DISTINCT name FROM t_c_collation ORDER BY name; + name +------ + ABC + DEF + GHI + abc + def + ghi +(6 rows) + +-- ====================================================================== +-- Test: String functions with C collation +-- ====================================================================== +SELECT name, length(name), upper(name), lower(name) +FROM t_c_collation ORDER BY name; + name | length | upper | lower +------+--------+-------+------- + ABC | 3 | ABC | abc + DEF | 3 | DEF | def + GHI | 3 | GHI | ghi + abc | 3 | ABC | abc + def | 3 | DEF | def + ghi | 3 | GHI | ghi +(6 rows) + +-- min/max aggregate should respect C collation +SELECT min(name), max(name) FROM t_c_collation; + min | max +-----+----- + ABC | ghi +(1 row) + +-- string_agg with ORDER BY using C collation +SELECT string_agg(name, ',' ORDER BY name) FROM t_c_collation; + string_agg +------------------------- + ABC,DEF,GHI,abc,def,ghi +(1 row) + +-- ====================================================================== +-- Test: LIKE / pattern matching with C collation +-- ====================================================================== +-- LIKE is byte-based under C collation +SELECT name FROM t_c_collation WHERE name LIKE 'a%' ORDER BY name; + name +------ + abc +(1 row) + +SELECT name FROM t_c_collation WHERE name LIKE 'A%' ORDER BY name; + name +------ + ABC +(1 row) + +-- BETWEEN uses C collation ordering +-- Under C: 'D' < 'Z' < 'a', so BETWEEN 'A' AND 'Z' gets only uppercase +SELECT name FROM t_c_collation WHERE name BETWEEN 'A' AND 'Z' ORDER BY name; + name +------ + ABC + DEF + GHI +(3 rows) + +-- ====================================================================== +-- Test: Index scan with C collation +-- ====================================================================== +CREATE INDEX idx_c_name ON t_c_collation (name); +ANALYZE t_c_collation; +-- Index scan should respect C collation ordering +SET enable_seqscan TO off; +SELECT name FROM t_c_collation WHERE name > 'Z' ORDER BY name; + name +------ + abc + def + ghi +(3 rows) + +SELECT name FROM t_c_collation WHERE name <= 'Z' ORDER BY name; + name +------ + ABC + DEF + GHI +(3 rows) + +RESET enable_seqscan; +DROP INDEX idx_c_name; +-- ====================================================================== +-- Test: CASE expression with C collation comparison +-- ====================================================================== +SELECT name, + CASE WHEN name < 'a' THEN 'uppercase' ELSE 'lowercase' END as case_type +FROM t_c_collation +ORDER BY name; + name | case_type +------+----------- + ABC | uppercase + DEF | uppercase + GHI | uppercase + abc | lowercase + def | lowercase + ghi | lowercase +(6 rows) + +-- ====================================================================== +-- Test: Aggregate functions with C collation +-- ====================================================================== +-- count with GROUP BY preserves C collation grouping +SELECT name, count(*), sum(id) +FROM t_c_collation GROUP BY name ORDER BY name; + name | count | sum +------+-------+----- + ABC | 1 | 2 + DEF | 1 | 4 + GHI | 1 | 6 + abc | 1 | 1 + def | 1 | 3 + ghi | 1 | 5 +(6 rows) + +-- array_agg with ORDER BY should use C collation +SELECT array_agg(name ORDER BY name) FROM t_c_collation; + array_agg +--------------------------- + {ABC,DEF,GHI,abc,def,ghi} +(1 row) + +-- min/max on varchar(50) COLLATE "C" column +SELECT min(val), max(val) FROM t_c_collation; + min | max +-------+-------- + APPLE | cherry +(1 row) + +-- ====================================================================== +-- Test: LIMIT / OFFSET with C collation ORDER BY +-- ====================================================================== +SELECT name FROM t_c_collation ORDER BY name LIMIT 3; + name +------ + ABC + DEF + GHI +(3 rows) + +SELECT name FROM t_c_collation ORDER BY name LIMIT 3 OFFSET 3; + name +------ + abc + def + ghi +(3 rows) + +-- ====================================================================== +-- Test: EXCEPT with C collation +-- ====================================================================== +-- All uppercase names (< 'a' under C) except DEF +SELECT name FROM t_c_collation WHERE name < 'a' +EXCEPT +SELECT name FROM t_c_collation WHERE name = 'DEF' +ORDER BY name; + name +------ + ABC + GHI +(2 rows) + +-- ====================================================================== +-- Test: INSERT INTO ... SELECT preserves C collation +-- ====================================================================== +CREATE TABLE t_c_copy (id int, name text COLLATE "C") DISTRIBUTED BY (id); +INSERT INTO t_c_copy SELECT id, name FROM t_c_collation; +SELECT name FROM t_c_copy ORDER BY name; + name +------ + ABC + DEF + GHI + abc + def + ghi +(6 rows) + +DROP TABLE t_c_copy; +-- ====================================================================== +-- Test: CTAS with C collation column +-- ====================================================================== +CREATE TABLE t_c_ctas AS SELECT id, name FROM t_c_collation DISTRIBUTED BY (id); +-- Verify the new table inherits C collation +SELECT name FROM t_c_ctas ORDER BY name; + name +------ + ABC + DEF + GHI + abc + def + ghi +(6 rows) + +DROP TABLE t_c_ctas; +-- ====================================================================== +-- Test: Multiple aggregates in same query +-- ====================================================================== +SELECT min(name), max(name), min(val), max(val), + count(DISTINCT name) +FROM t_c_collation; + min | max | min | max | count +-----+-----+-------+--------+------- + ABC | ghi | APPLE | cherry | 6 +(1 row) + +-- ====================================================================== +-- Test: Window functions with C collation ordering +-- ====================================================================== +-- lag/lead should follow C collation order +SELECT name, + lag(name) OVER (ORDER BY name) as prev_name, + lead(name) OVER (ORDER BY name) as next_name +FROM t_c_collation +ORDER BY name; + name | prev_name | next_name +------+-----------+----------- + ABC | | DEF + DEF | ABC | GHI + GHI | DEF | abc + abc | GHI | def + def | abc | ghi + ghi | def | +(6 rows) + +-- ntile with C collation partitioning +SELECT name, + ntile(2) OVER (ORDER BY name) as bucket +FROM t_c_collation +ORDER BY name; + name | bucket +------+-------- + ABC | 1 + DEF | 1 + GHI | 1 + abc | 2 + def | 2 + ghi | 2 +(6 rows) + +-- ====================================================================== +-- Test: Nested subquery with C collation +-- ====================================================================== +SELECT name FROM t_c_collation +WHERE name IN (SELECT name FROM t_c_collation WHERE name < 'a') +ORDER BY name; + name +------ + ABC + DEF + GHI +(3 rows) + +-- Scalar subquery with min/max on C collation +SELECT name, + (SELECT min(b.name) FROM t_c_collation b WHERE b.name > a.name) as next_min +FROM t_c_collation a +ORDER BY name; + name | next_min +------+---------- + ABC | DEF + DEF | GHI + GHI | abc + abc | def + def | ghi + ghi | +(6 rows) + +-- ====================================================================== +-- Test: UPDATE/DELETE with C collation WHERE clause +-- ====================================================================== +CREATE TABLE t_c_dml (id int, name text COLLATE "C") DISTRIBUTED BY (id); +INSERT INTO t_c_dml SELECT id, name FROM t_c_collation; +-- DELETE rows where name < 'a' (uppercase under C collation) +DELETE FROM t_c_dml WHERE name < 'a'; +SELECT name FROM t_c_dml ORDER BY name; + name +------ + abc + def + ghi +(3 rows) + +-- Re-insert and UPDATE +INSERT INTO t_c_dml SELECT id, name FROM t_c_collation WHERE name < 'a'; +UPDATE t_c_dml SET name = name || '_updated' WHERE name < 'a'; +SELECT name FROM t_c_dml ORDER BY name; + name +------------- + ABC_updated + DEF_updated + GHI_updated + abc + def + ghi +(6 rows) + +DROP TABLE t_c_dml; +-- ====================================================================== +-- Cleanup +-- ====================================================================== +RESET optimizer_trace_fallback; +RESET optimizer; +DROP SCHEMA collate_orca CASCADE; +NOTICE: drop cascades to 3 other objects +DETAIL: drop cascades to table t_c_collation +drop cascades to table t_default_collation +drop cascades to table t_mixed_collation diff --git a/src/test/regress/expected/create_index_optimizer.out b/src/test/regress/expected/create_index_optimizer.out index 65f5f92b8bd..dd3751bb6fc 100644 --- a/src/test/regress/expected/create_index_optimizer.out +++ b/src/test/regress/expected/create_index_optimizer.out @@ -2007,12 +2007,11 @@ EXPLAIN (COSTS OFF) Finalize Aggregate -> Gather Motion 3:1 (slice1; segments: 3) -> Partial Aggregate - -> Bitmap Heap Scan on dupindexcols - Recheck Cond: ((f1 >= 'WA'::text) AND (f1 <= 'ZZZ'::text) AND (id < 1000) AND (f1 ~<~ 'YX'::text)) - -> Bitmap Index Scan on dupindexcols_i - Index Cond: ((f1 >= 'WA'::text) AND (f1 <= 'ZZZ'::text) AND (id < 1000) AND (f1 ~<~ 'YX'::text)) - Optimizer: Postgres query optimizer -(8 rows) + -> Index Scan using dupindexcols_i on dupindexcols + Index Cond: ((f1 >= 'WA'::text) AND (f1 <= 'ZZZ'::text) AND (id < 1000)) + Filter: (f1 ~<~ 'YX'::text) + Optimizer: GPORCA +(7 rows) SELECT count(*) FROM dupindexcols WHERE f1 BETWEEN 'WA' AND 'ZZZ' and id < 1000 and f1 ~<~ 'YX'; diff --git a/src/test/regress/expected/equivclass_optimizer.out b/src/test/regress/expected/equivclass_optimizer.out index dddbb129f59..c2052a52ed8 100644 --- a/src/test/regress/expected/equivclass_optimizer.out +++ b/src/test/regress/expected/equivclass_optimizer.out @@ -503,11 +503,15 @@ create temp view overview as select f1::information_schema.sql_identifier as sqli, f2 from undername; explain (costs off) -- this should not require a sort select * from overview where sqli = 'foo' order by sqli; - QUERY PLAN ------------------------------------------- - Gather Motion 1:1 (slice1; segments: 1) - -> Seq Scan on undername - Filter: (f1 = 'foo'::name) - Optimizer: Postgres query optimizer -(4 rows) + QUERY PLAN +---------------------------------------------------------------------------------- + Gather Motion 3:1 (slice1; segments: 3) + Merge Key: ((f1)::information_schema.sql_identifier) + -> Sort + Sort Key: ((f1)::information_schema.sql_identifier) + -> Result + Filter: ((((f1)::information_schema.sql_identifier))::name = 'foo'::name) + -> Seq Scan on undername + Optimizer: GPORCA +(8 rows) diff --git a/src/test/regress/expected/generated_optimizer.out b/src/test/regress/expected/generated_optimizer.out index c1a1eeec122..e5d6027660d 100644 --- a/src/test/regress/expected/generated_optimizer.out +++ b/src/test/regress/expected/generated_optimizer.out @@ -11,7 +11,6 @@ CREATE TABLE gtest0 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (55) STORED); CREATE TABLE gtest1 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED); SELECT table_name, column_name, column_default, is_nullable, is_generated, generation_expression FROM information_schema.columns WHERE table_name LIKE 'gtest_' ORDER BY 1, 2; INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation table_name | column_name | column_default | is_nullable | is_generated | generation_expression ------------+-------------+----------------+-------------+--------------+----------------------- gtest0 | a | | NO | NEVER | @@ -22,7 +21,6 @@ DETAIL: Falling back to Postgres-based planner because GPORCA does not support SELECT table_name, column_name, dependent_column FROM information_schema.column_column_usage ORDER BY 1, 2, 3; INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation table_name | column_name | dependent_column ------------+-------------+------------------ gtest1 | a | b @@ -30,25 +28,18 @@ DETAIL: Falling back to Postgres-based planner because GPORCA does not support \d gtest1 INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation Table "public.gtest1" Column | Type | Collation | Nullable | Default --------+---------+-----------+----------+------------------------------------ @@ -283,23 +274,17 @@ SELECT * FROM gtest1_1; \d gtest1_1 INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation Table "public.gtest1_1" Column | Type | Collation | Nullable | Default --------+---------+-----------+----------+------------------------------------ @@ -330,23 +315,17 @@ NOTICE: merging column "a" with inherited definition NOTICE: merging column "b" with inherited definition \d gtest_normal_child INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation Table "public.gtest_normal_child" Column | Type | Collation | Nullable | Default --------+---------+-----------+----------+------------------------------------ @@ -565,25 +544,18 @@ CREATE TABLE gtest10 (a int PRIMARY KEY, b int, c int GENERATED ALWAYS AS (b * 2 ALTER TABLE gtest10 DROP COLUMN b; \d gtest10 INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation Table "public.gtest10" Column | Type | Collation | Nullable | Default --------+---------+-----------+----------+--------- @@ -678,25 +650,18 @@ CREATE INDEX gtest22c_expr_idx ON gtest22c ((b * 3)); CREATE INDEX gtest22c_pred_idx ON gtest22c (a) WHERE b > 0; \d gtest22c INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation Table "public.gtest22c" Column | Type | Collation | Nullable | Default --------+---------+-----------+----------+------------------------------------ @@ -769,31 +734,24 @@ ERROR: invalid ON DELETE action for foreign key constraint containing generated CREATE TABLE gtest23b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED REFERENCES gtest23a (x)); \d gtest23b INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation Table "public.gtest23b" Column | Type | Collation | Nullable | Default --------+---------+-----------+----------+------------------------------------ @@ -892,25 +850,18 @@ SELECT * FROM gtest25 ORDER BY a; \d gtest25 INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation Table "public.gtest25" Column | Type | Collation | Nullable | Default --------+------------------+-----------+----------+------------------------------------------------------ @@ -938,23 +889,17 @@ DETAIL: Column "a" is used by generated column "x". ALTER TABLE gtest27 ALTER COLUMN x TYPE numeric; \d gtest27 INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation Table "public.gtest27" Column | Type | Collation | Nullable | Default --------+---------+-----------+----------+-------------------------------------------- @@ -983,23 +928,17 @@ ALTER TABLE gtest27 ADD COLUMN x bigint GENERATED ALWAYS AS ((a + b) * 2) STORED; \d gtest27 INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation Table "public.gtest27" Column | Type | Collation | Nullable | Default --------+--------+-----------+----------+------------------------------------------ @@ -1016,23 +955,17 @@ ERROR: cannot alter type of a column used by a generated column DETAIL: Column "a" is used by generated column "x". \d gtest27 INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation Table "public.gtest27" Column | Type | Collation | Nullable | Default --------+--------+-----------+----------+------------------------------------------ @@ -1072,23 +1005,17 @@ SELECT * FROM gtest29; \d gtest29 INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation Table "public.gtest29" Column | Type | Collation | Nullable | Default --------+---------+-----------+----------+--------- @@ -1100,23 +1027,17 @@ Distributed by: (a) ALTER TABLE gtest29 DROP COLUMN a; -- should not drop b \d gtest29 INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation Table "public.gtest29" Column | Type | Collation | Nullable | Default --------+---------+-----------+----------+--------- @@ -1132,23 +1053,17 @@ CREATE TABLE gtest30_1 () INHERITS (gtest30); ALTER TABLE gtest30 ALTER COLUMN b DROP EXPRESSION; \d gtest30 INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation Table "public.gtest30" Column | Type | Collation | Nullable | Default --------+---------+-----------+----------+--------- @@ -1159,23 +1074,17 @@ Distributed by: (a) \d gtest30_1 INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation Table "public.gtest30_1" Column | Type | Collation | Nullable | Default --------+---------+-----------+----------+--------- @@ -1195,23 +1104,17 @@ ALTER TABLE ONLY gtest30 ALTER COLUMN b DROP EXPRESSION; -- error ERROR: ALTER TABLE / DROP EXPRESSION must be applied to child tables too \d gtest30 INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation Table "public.gtest30" Column | Type | Collation | Nullable | Default --------+---------+-----------+----------+------------------------------------ @@ -1222,23 +1125,17 @@ Distributed by: (a) \d gtest30_1 INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation Table "public.gtest30_1" Column | Type | Collation | Nullable | Default --------+---------+-----------+----------+------------------------------------ @@ -1427,23 +1324,17 @@ ALTER TABLE gtest28a DROP COLUMN a; CREATE TABLE gtest28b (LIKE gtest28a INCLUDING GENERATED); \d gtest28* INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation Table "public.gtest28a" Column | Type | Collation | Nullable | Default --------+---------+-----------+----------+------------------------------------ @@ -1455,19 +1346,14 @@ Distributed randomly INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation Table "public.gtest28b" Column | Type | Collation | Nullable | Default --------+---------+-----------+----------+------------------------------------ diff --git a/src/test/regress/expected/gp_array_agg_optimizer.out b/src/test/regress/expected/gp_array_agg_optimizer.out index 94af2d0d82f..01cee60bc9f 100644 --- a/src/test/regress/expected/gp_array_agg_optimizer.out +++ b/src/test/regress/expected/gp_array_agg_optimizer.out @@ -341,24 +341,18 @@ from arrtest; $query$ AS qry \gset EXPLAIN (COSTS OFF, VERBOSE) :qry ; -INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation - QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - Finalize Aggregate + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------ + Aggregate Output: array_agg(a), array_dims(array_agg(b)), array_dims(array_agg(c)), array_agg(d), array_dims(array_agg(e)), array_agg(f), array_agg(g) -> Gather Motion 3:1 (slice1; segments: 3) - Output: (PARTIAL array_agg(a)), (PARTIAL array_agg(b)), (PARTIAL array_agg(c)), (PARTIAL array_agg(d)), (PARTIAL array_agg(e)), (PARTIAL array_agg(f)), (PARTIAL array_agg(g)) - -> Partial Aggregate - Output: PARTIAL array_agg(a), PARTIAL array_agg(b), PARTIAL array_agg(c), PARTIAL array_agg(d), PARTIAL array_agg(e), PARTIAL array_agg(f), PARTIAL array_agg(g) - -> Seq Scan on test_gp_array_agg.arrtest - Output: a, b, c, d, e, f, g - Optimizer: Postgres query optimizer -(9 rows) + Output: a, b, c, d, e, f, g + -> Seq Scan on test_gp_array_agg.arrtest + Output: a, b, c, d, e, f, g + Optimizer: GPORCA +(7 rows) :qry ; -INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation agg_a | dims_b | dims_c | agg_d | dims_e | agg_f | agg_g ---------------+----------------------+------------+-------------------------------+------------+-----------------------------------+--------------------------- {{1,2},{1,2}} | [1:2][1:2][1:2][1:2] | [1:2][1:1] | {{{elt1,elt2}},{{elt1,elt2}}} | [1:2][1:2] | {{"abc ",abcde},{"abc ",abcde}} | {{abc,abcde},{abc,abcde}} diff --git a/src/test/regress/expected/gporca_optimizer.out b/src/test/regress/expected/gporca_optimizer.out index b8b83e01ab2..86443391ad7 100644 --- a/src/test/regress/expected/gporca_optimizer.out +++ b/src/test/regress/expected/gporca_optimizer.out @@ -9690,8 +9690,6 @@ create table orca.arrtest ( insert into orca.arrtest (a[1:5], b[1:1][1:2][1:2], c, d) values ('{1,2,3,4,5}', '{{{0,0},{1,2}}}', '{}', '{}'); select a[1:3], b[1][2][1], c[1], d[1][1] FROM orca.arrtest order by 1,2,3,4; -INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation a | b | c | d ---------+---+---+--- {1,2,3} | 1 | | diff --git a/src/test/regress/expected/rowsecurity_optimizer.out b/src/test/regress/expected/rowsecurity_optimizer.out index 5717d5bf9a7..927cab401ff 100644 --- a/src/test/regress/expected/rowsecurity_optimizer.out +++ b/src/test/regress/expected/rowsecurity_optimizer.out @@ -48,7 +48,6 @@ INSERT INTO uaccount VALUES ('regress_rls_carol', 2), ('regress_rls_dave', 3); INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation ANALYZE uaccount; CREATE TABLE category ( cid int primary key, @@ -81,7 +80,6 @@ INSERT INTO document VALUES ( 9, 22, 1, 'regress_rls_dave', 'awesome science fiction'), (10, 33, 2, 'regress_rls_dave', 'awesome technology book'); INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation ANALYZE document; ALTER TABLE document ENABLE ROW LEVEL SECURITY; -- user's security level must be higher than or equal to document's @@ -105,7 +103,6 @@ CREATE POLICY p1r ON document AS RESTRICTIVE TO regress_rls_dave USING (cid <> 44); \dp INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation Access privileges Schema | Name | Type | Access privileges | Column privileges | Policies --------------------+----------+-------+---------------------------------------------+-------------------+-------------------------------------------- @@ -127,31 +124,24 @@ DETAIL: Falling back to Postgres-based planner because GPORCA does not support \d document INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation Table "regress_rls_schema.document" Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- @@ -179,7 +169,6 @@ Distributed by: (did) SELECT * FROM pg_policies WHERE schemaname = 'regress_rls_schema' AND tablename = 'document' ORDER BY policyname; INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation schemaname | tablename | policyname | permissive | roles | cmd | qual | with_check --------------------+-----------+------------+-------------+--------------------+-----+--------------------------------------------+------------ regress_rls_schema | document | p1 | PERMISSIVE | {public} | ALL | (dlevel <= ( SELECT uaccount.seclv +| @@ -444,8 +433,6 @@ ALTER POLICY p1 ON document USING (dauthor = current_user); -- viewpoint from regress_rls_bob again SET SESSION AUTHORIZATION regress_rls_bob; SELECT * FROM document WHERE f_leak(dtitle) ORDER BY did; -INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation NOTICE: f_leak => my second novel NOTICE: f_leak => my first novel NOTICE: f_leak => my second manga @@ -461,8 +448,6 @@ NOTICE: f_leak => my first manga (5 rows) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER by did; -INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation NOTICE: f_leak => my second novel NOTICE: f_leak => my first novel NOTICE: f_leak => my science fiction @@ -480,8 +465,6 @@ NOTICE: f_leak => my second manga -- viewpoint from rls_regres_carol again SET SESSION AUTHORIZATION regress_rls_carol; SELECT * FROM document WHERE f_leak(dtitle) ORDER BY did; -INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation NOTICE: f_leak => great technology book NOTICE: f_leak => great science fiction NOTICE: f_leak => great manga @@ -493,8 +476,6 @@ NOTICE: f_leak => great manga (3 rows) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER by did; -INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation NOTICE: f_leak => great technology book NOTICE: f_leak => great manga NOTICE: f_leak => great science fiction @@ -506,31 +487,28 @@ NOTICE: f_leak => great science fiction (3 rows) EXPLAIN (COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle); -INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation QUERY PLAN ---------------------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) -> Seq Scan on document Filter: ((dauthor = 'regress_rls_carol'::name) AND f_leak(dtitle)) - Optimizer: Postgres query optimizer + Optimizer: GPORCA (4 rows) EXPLAIN (COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle); -INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation QUERY PLAN ---------------------------------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) - -> Hash Join - Hash Cond: (category.cid = document.cid) - -> Broadcast Motion 3:3 (slice2; segments: 3) - -> Seq Scan on category - -> Hash + -> Nested Loop + Join Filter: true + -> Redistribute Motion 3:3 (slice2; segments: 3) + Hash Key: document.cid -> Seq Scan on document Filter: ((dauthor = 'regress_rls_carol'::name) AND f_leak(dtitle)) - Optimizer: Postgres query optimizer -(9 rows) + -> Index Scan using category_pkey on category + Index Cond: (cid = document.cid) + Optimizer: GPORCA +(10 rows) -- interaction of FK/PK constraints SET SESSION AUTHORIZATION regress_rls_alice; @@ -542,8 +520,6 @@ ALTER TABLE category ENABLE ROW LEVEL SECURITY; -- cannot delete PK referenced by invisible FK SET SESSION AUTHORIZATION regress_rls_bob; SELECT * FROM document d FULL OUTER JOIN category c on d.cid = c.cid ORDER BY d.did, c.cid; -INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation did | cid | dlevel | dauthor | dtitle | cid | cname -----+-----+--------+-----------------+--------------------+-----+------------ 1 | 11 | 1 | regress_rls_bob | my first novel | 11 | novel @@ -556,15 +532,11 @@ DETAIL: Falling back to Postgres-based planner because GPORCA does not support -- GPDB: referential integrity checks are not enforced -- start_ignore -DELETE FROM category WHERE cid = 33; -- fails with FK violation -ERROR: update or delete on table "category" violates foreign key constraint "document_cid_fkey" on table "document" -DETAIL: Key is still referenced from table "document". +-- DELETE FROM category WHERE cid = 33; -- fails with FK violation -- end_ignore -- can insert FK referencing invisible PK SET SESSION AUTHORIZATION regress_rls_carol; SELECT * FROM document d FULL OUTER JOIN category c on d.cid = c.cid ORDER BY d.did, c.cid; -INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation did | cid | dlevel | dauthor | dtitle | cid | cname -----+-----+--------+-------------------+-----------------------+-----+----------------- 6 | 22 | 1 | regress_rls_carol | great science fiction | 22 | science fiction @@ -582,8 +554,6 @@ INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: View with WITH CHECK OPTION ERROR: duplicate key value violates unique constraint "document_pkey" SELECT * FROM document WHERE did = 8; -- and confirm we can't see it -INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation did | cid | dlevel | dauthor | dtitle -----+-----+--------+---------+-------- (0 rows) @@ -1100,7 +1070,6 @@ INSERT INTO part_document VALUES ( 9, 11, 1, 'regress_rls_dave', 'awesome science fiction'), (10, 99, 2, 'regress_rls_dave', 'awesome technology book'); INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation ALTER TABLE part_document ENABLE ROW LEVEL SECURITY; -- Create policy on parent -- user's security level must be higher than or equal to document's @@ -1111,29 +1080,22 @@ CREATE POLICY pp1r ON part_document AS RESTRICTIVE TO regress_rls_dave USING (cid < 55); \d+ part_document INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Queries on master-only tables INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation Partitioned table "regress_rls_schema.part_document" Column | Type | Collation | Nullable | Default | Storage | Stats target | Description ---------+---------+-----------+----------+---------+----------+--------------+------------- @@ -1158,7 +1120,6 @@ Distributed by: (did) SELECT * FROM pg_policies WHERE schemaname = 'regress_rls_schema' AND tablename like '%part_document%' ORDER BY policyname; INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation schemaname | tablename | policyname | permissive | roles | cmd | qual | with_check --------------------+---------------+------------+-------------+--------------------+-----+--------------------------------------------+------------ regress_rls_schema | part_document | pp1 | PERMISSIVE | {public} | ALL | (dlevel <= ( SELECT uaccount.seclv +| @@ -1442,8 +1403,6 @@ ALTER POLICY pp1 ON part_document USING (dauthor = current_user); -- viewpoint from regress_rls_bob again SET SESSION AUTHORIZATION regress_rls_bob; SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did; -INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation NOTICE: f_leak => my second novel NOTICE: f_leak => my first novel NOTICE: f_leak => my first satire @@ -1461,8 +1420,6 @@ NOTICE: f_leak => my science textbook -- viewpoint from rls_regres_carol again SET SESSION AUTHORIZATION regress_rls_carol; SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did; -INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation NOTICE: f_leak => great science fiction NOTICE: f_leak => great satire NOTICE: f_leak => great technology book @@ -1474,20 +1431,13 @@ NOTICE: f_leak => great technology book (3 rows) EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle); -INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation - QUERY PLAN + QUERY PLAN ---------------------------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) - -> Append - -> Seq Scan on part_document_fiction part_document_1 - Filter: ((dauthor = 'regress_rls_carol'::name) AND f_leak(dtitle)) - -> Seq Scan on part_document_satire part_document_2 - Filter: ((dauthor = 'regress_rls_carol'::name) AND f_leak(dtitle)) - -> Seq Scan on part_document_nonfiction part_document_3 - Filter: ((dauthor = 'regress_rls_carol'::name) AND f_leak(dtitle)) - Optimizer: Postgres query optimizer -(9 rows) + -> Dynamic Seq Scan on part_document + Number of partitions to scan: 3 (out of 3) + Filter: ((dauthor = 'regress_rls_carol'::name) AND f_leak(dtitle)) +(5 rows) -- database superuser does bypass RLS policy when enabled RESET SESSION AUTHORIZATION; @@ -2974,8 +2924,6 @@ CREATE POLICY p4 ON x1 FOR DELETE USING (a < 8); ALTER TABLE x1 ENABLE ROW LEVEL SECURITY; SET SESSION AUTHORIZATION regress_rls_bob; SELECT * FROM x1 WHERE f_leak(b) ORDER BY a ASC; -INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation NOTICE: f_leak => bcd NOTICE: f_leak => def NOTICE: f_leak => abc @@ -3013,8 +2961,6 @@ NOTICE: f_leak => fgh SET SESSION AUTHORIZATION regress_rls_carol; SELECT * FROM x1 WHERE f_leak(b) ORDER BY a ASC; -INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation NOTICE: f_leak => fgh_updt NOTICE: f_leak => cde NOTICE: f_leak => fgh @@ -3052,7 +2998,7 @@ NOTICE: f_leak => fgh_updt DELETE FROM x1 WHERE f_leak(b) RETURNING *; INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation +DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: RETURNING clause NOTICE: f_leak => cde_updt NOTICE: f_leak => fgh_updt_updt NOTICE: f_leak => fgh_updt @@ -3439,7 +3385,6 @@ SELECT polname, relname JOIN pg_class pc ON (pc.oid = pol.polrelid) WHERE relname = 't1'; INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation polname | relname ---------+--------- p1 | t1 @@ -3451,7 +3396,6 @@ SELECT polname, relname JOIN pg_class pc ON (pc.oid = pol.polrelid) WHERE relname = 't1'; INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation polname | relname ---------+--------- p2 | t1 @@ -4051,7 +3995,6 @@ ALTER TABLE coll_t ENABLE ROW LEVEL SECURITY; GRANT SELECT ON coll_t TO regress_rls_alice; SELECT (string_to_array(polqual, ':'))[7] AS inputcollid FROM pg_policy WHERE polrelid = 'coll_t'::regclass; INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation inputcollid ------------------ inputcollid 950 @@ -4060,7 +4003,6 @@ DETAIL: Falling back to Postgres-based planner because GPORCA does not support SET SESSION AUTHORIZATION regress_rls_alice; SELECT * FROM coll_t; INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation c ----- bar @@ -4711,8 +4653,6 @@ create function rls_f () returns setof rls_t prepare q as select current_user, * from rls_f(); set role regress_rls_alice; execute q; -INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation current_user | c -------------------+------------------ regress_rls_alice | invisible to bob @@ -4720,8 +4660,6 @@ DETAIL: Falling back to Postgres-based planner because GPORCA does not support set role regress_rls_bob; execute q; -INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation current_user | c --------------+--- (0 rows) diff --git a/src/test/regress/expected/select_views_optimizer.out b/src/test/regress/expected/select_views_optimizer.out index ace4125ad5d..5bd8e6f3b6a 100644 --- a/src/test/regress/expected/select_views_optimizer.out +++ b/src/test/regress/expected/select_views_optimizer.out @@ -1325,30 +1325,20 @@ SET SESSION AUTHORIZATION regress_alice; -- scenario: if a qualifier with tiny-cost is given, it shall be launched -- prior to the security policy of the view. -- --- start_ignore --- GPDB_92_MERGE_FIXME: ORCA doesn't seem to order the predicates based on the --- cost of the function, do we want to do that? --- end_ignore SELECT * FROM my_property_normal WHERE f_leak(passwd); -NOTICE: f_leak => passwd123 (seg1 slice1 127.0.0.1:7003 pid=9756) -NOTICE: f_leak => beafsteak (seg1 slice1 127.0.0.1:7003 pid=9756) -NOTICE: f_leak => hamburger (seg1 slice1 127.0.0.1:7003 pid=9756) +NOTICE: f_leak => passwd123 (seg1 slice1 127.0.0.1:7003 pid=369832) cid | name | tel | passwd -----+---------------+------------------+----------- 101 | regress_alice | +81-12-3456-7890 | passwd123 (1 row) --- start_ignore --- GPDB_92_MERGE_FIXME: ORCA doesn't seem to order the predicates based on the --- cost of the function, do we want to do that? --- end_ignore EXPLAIN (COSTS OFF) SELECT * FROM my_property_normal WHERE f_leak(passwd); - QUERY PLAN ------------------------------------------------------------- + QUERY PLAN +--------------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) -> Seq Scan on customer - Filter: (f_leak(passwd) AND (name = 'regress_alice'::name)) - Optimizer: Postgres query optimizer + Filter: ((name = 'regress_alice'::name) AND f_leak(passwd)) + Optimizer: GPORCA (4 rows) SELECT * FROM my_property_secure WHERE f_leak(passwd); @@ -1375,12 +1365,8 @@ EXPLAIN (COSTS OFF) SELECT * FROM my_property_secure WHERE f_leak(passwd); -- SELECT * FROM my_property_normal v WHERE f_leak('passwd') AND f_leak(passwd); -NOTICE: f_leak => passwd (seg1 slice1 127.0.0.1:7003 pid=16817) -NOTICE: f_leak => passwd123 (seg1 slice1 127.0.0.1:7003 pid=16817) -NOTICE: f_leak => passwd (seg1 slice1 127.0.0.1:7003 pid=16817) -NOTICE: f_leak => beafsteak (seg1 slice1 127.0.0.1:7003 pid=16817) -NOTICE: f_leak => passwd (seg1 slice1 127.0.0.1:7003 pid=16817) -NOTICE: f_leak => hamburger (seg1 slice1 127.0.0.1:7003 pid=16817) +NOTICE: f_leak => passwd (seg1 slice1 127.0.0.1:7003 pid=369832) +NOTICE: f_leak => passwd123 (seg1 slice1 127.0.0.1:7003 pid=369832) cid | name | tel | passwd -----+---------------+------------------+----------- 101 | regress_alice | +81-12-3456-7890 | passwd123 @@ -1388,12 +1374,12 @@ NOTICE: f_leak => hamburger (seg1 slice1 127.0.0.1:7003 pid=16817) EXPLAIN (COSTS OFF) SELECT * FROM my_property_normal v WHERE f_leak('passwd') AND f_leak(passwd); - QUERY PLAN ---------------------------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------------------------ Gather Motion 3:1 (slice1; segments: 3) -> Seq Scan on customer - Filter: (f_leak('passwd'::text) AND f_leak(passwd) AND (name = 'regress_alice'::name)) - Optimizer: Postgres query optimizer + Filter: ((name = 'regress_alice'::name) AND f_leak('passwd'::text) AND f_leak(passwd)) + Optimizer: GPORCA (4 rows) SELECT * FROM my_property_secure v @@ -1434,17 +1420,17 @@ NOTICE: f_leak => 9801-2345-6789-0123 (seg1 slice1 127.0.0.1:7003 pid=16817) (1 row) EXPLAIN (COSTS OFF) SELECT * FROM my_credit_card_normal WHERE f_leak(cnum); - QUERY PLAN --------------------------------------------------------------------- + QUERY PLAN +----------------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) - -> Hash Join - Hash Cond: ((r.cid = l.cid) AND (r.dist_key = l.dist_key)) + -> Nested Loop + Join Filter: true -> Seq Scan on credit_card r Filter: f_leak(cnum) - -> Hash - -> Seq Scan on customer l - Filter: (name = 'regress_alice'::name) - Optimizer: Postgres query optimizer + -> Index Scan using customer_dist_key_cid_key on customer l + Index Cond: ((dist_key = r.dist_key) AND (cid = r.cid)) + Filter: (name = 'regress_alice'::name) + Optimizer: GPORCA (9 rows) SELECT * FROM my_credit_card_secure WHERE f_leak(cnum); @@ -1550,9 +1536,7 @@ EXPLAIN (COSTS OFF) SELECT * FROM my_credit_card_usage_secure PREPARE p1 AS SELECT * FROM my_property_normal WHERE f_leak(passwd); PREPARE p2 AS SELECT * FROM my_property_secure WHERE f_leak(passwd); EXECUTE p1; -NOTICE: f_leak => passwd123 (seg1 slice1 127.0.0.1:7003 pid=16817) -NOTICE: f_leak => beafsteak (seg1 slice1 127.0.0.1:7003 pid=16817) -NOTICE: f_leak => hamburger (seg1 slice1 127.0.0.1:7003 pid=16817) +NOTICE: f_leak => passwd123 (seg1 slice1 127.0.0.1:7003 pid=369832) cid | name | tel | passwd -----+---------------+------------------+----------- 101 | regress_alice | +81-12-3456-7890 | passwd123 @@ -1577,9 +1561,7 @@ NOTICE: f_leak => passwd123 (seg1 slice1 127.0.0.1:7003 pid=16817) (1 row) EXECUTE p2; -- To be perform as a view without security-barrier -NOTICE: f_leak => passwd123 (seg1 slice1 127.0.0.1:7003 pid=16817) -NOTICE: f_leak => beafsteak (seg1 slice1 127.0.0.1:7003 pid=16817) -NOTICE: f_leak => hamburger (seg1 slice1 127.0.0.1:7003 pid=16817) +NOTICE: f_leak => passwd123 (seg1 slice1 127.0.0.1:7003 pid=369832) cid | name | tel | passwd -----+---------------+------------------+----------- 101 | regress_alice | +81-12-3456-7890 | passwd123 diff --git a/src/test/regress/expected/stats_ext_optimizer.out b/src/test/regress/expected/stats_ext_optimizer.out index dafbf0a28b4..f8a7b5c24ca 100644 --- a/src/test/regress/expected/stats_ext_optimizer.out +++ b/src/test/regress/expected/stats_ext_optimizer.out @@ -3044,7 +3044,7 @@ ANALYZE expr_stats; SELECT * FROM check_estimated_rows('SELECT * FROM expr_stats WHERE a = 0 AND (b || c) <= ''z'' AND (c || b) >= ''0'''); estimated | actual -----------+-------- - 11 | 100 + 68 | 100 (1 row) CREATE STATISTICS expr_stats_1 (mcv) ON a, b, (b || c), (c || b) FROM expr_stats; @@ -3052,7 +3052,7 @@ ANALYZE expr_stats; SELECT * FROM check_estimated_rows('SELECT * FROM expr_stats WHERE a = 0 AND (b || c) <= ''z'' AND (c || b) >= ''0'''); estimated | actual -----------+-------- - 100 | 100 + 68 | 100 (1 row) DROP TABLE expr_stats; diff --git a/src/test/regress/expected/subselect_optimizer.out b/src/test/regress/expected/subselect_optimizer.out index 5b20a89461a..a93342b2a56 100644 --- a/src/test/regress/expected/subselect_optimizer.out +++ b/src/test/regress/expected/subselect_optimizer.out @@ -799,17 +799,31 @@ select * from outer_text where (f1, f2) not in (select * from inner_text); -- explain (verbose, costs off) select 'foo'::text in (select 'bar'::name union all select 'bar'::name); - QUERY PLAN -------------------------------------- - Result - Output: (hashed SubPlan 1) - SubPlan 1 - -> Append - -> Result - Output: 'bar'::name - -> Result - Output: 'bar'::name -(8 rows) + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Nested Loop Left Join + Output: ((CASE WHEN (count(*) = '0'::bigint) THEN '0'::bigint WHEN (count(*) = sum((CASE WHEN (('bar'::name) IS NULL) THEN 1 ELSE 0 END))) THEN '-1'::bigint ELSE count(*) END) > '0'::bigint) + Join Filter: true + -> Result + Output: true + -> Materialize + Output: (CASE WHEN (count(*) = '0'::bigint) THEN '0'::bigint WHEN (count(*) = sum((CASE WHEN (('bar'::name) IS NULL) THEN 1 ELSE 0 END))) THEN '-1'::bigint ELSE count(*) END) + -> Aggregate + Output: CASE WHEN (count(*) = '0'::bigint) THEN '0'::bigint WHEN (count(*) = sum((CASE WHEN (('bar'::name) IS NULL) THEN 1 ELSE 0 END))) THEN '-1'::bigint ELSE count(*) END + -> Result + Output: CASE WHEN (('bar'::name) IS NULL) THEN 1 ELSE 0 END + -> Append + -> Result + Output: ('bar'::name) + Filter: (('foo'::text = ('bar'::name)) OR (('bar'::name) IS NULL)) + -> Result + Output: 'bar'::name + -> Result + Output: ('bar'::name) + Filter: (('foo'::text = ('bar'::name)) OR (('bar'::name) IS NULL)) + -> Result + Output: 'bar'::name +(24 rows) select 'foo'::text in (select 'bar'::name union all select 'bar'::name); ?column? @@ -1081,18 +1095,18 @@ reset optimizer; explain (verbose, costs off) select x, x from (select (select current_database()) as x from (values(1),(2)) v(y)) ss; - QUERY PLAN --------------------------------------- - Values Scan on "*VALUES*" - Output: $0, $1 - InitPlan 1 (returns $0) - -> Result - Output: 'regression'::name - InitPlan 2 (returns $1) - -> Result - Output: 'regression'::name - Optimizer: Postgres query optimizer -(9 rows) + QUERY PLAN +------------------------------------------------------------------------------------------------- + Nested Loop Left Join + Output: ('regression'::name), ('regression'::name) + Join Filter: true + -> Values Scan on "Values" + Output: column1 + -> Materialize + Output: ('regression'::name) + -> Result + Output: 'regression'::name +(11 rows) explain (verbose, costs off) select x, x from @@ -1112,21 +1126,18 @@ explain (verbose, costs off) explain (verbose, costs off) select x, x from (select (select current_database() where y=y) as x from (values(1),(2)) v(y)) ss; - QUERY PLAN ----------------------------------------------------------------------- - Values Scan on "*VALUES*" - Output: (SubPlan 1), (SubPlan 2) - SubPlan 1 - -> Result - Output: 'regression'::name - One-Time Filter: ("*VALUES*".column1 = "*VALUES*".column1) - SubPlan 2 - -> Result - Output: 'regression'::name - One-Time Filter: ("*VALUES*".column1 = "*VALUES*".column1) - Optimizer: Postgres query optimizer - Settings: optimizer=on -(9 rows) + QUERY PLAN +--------------------------------------------------------------------------------------------- + Nested Loop Left Join + Output: ('regression'::name), ('regression'::name) + Join Filter: ("Values".column1 = "Values".column1) + -> Values Scan on "Values" + Output: column1 + -> Materialize + Output: ('regression'::name) + -> Result + Output: 'regression'::name +(11 rows) explain (verbose, costs off) select x, x from @@ -1768,9 +1779,9 @@ select * from x where f1 = 1; QUERY PLAN ------------------------------------------------------ Gather Motion 1:1 (slice1; segments: 1) - Output: subselect_tbl.f1, ('regression'::name) + Output: f1, ('regression'::name) -> Seq Scan on public.subselect_tbl - Output: subselect_tbl.f1, 'regression'::name + Output: f1, 'regression'::name Filter: (subselect_tbl.f1 = 1) Optimizer: Postgres query optimizer Settings: optimizer=off @@ -1854,56 +1865,82 @@ select * from x where f1 = 1; explain (verbose, costs off) with x as (select * from (select f1, current_database() as n from subselect_tbl) ss) select * from x, x x2 where x.n = x2.n; - QUERY PLAN ------------------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) - Output: share0_ref2.f1, share0_ref2.n, share0_ref1.f1, share0_ref1.n - -> Hash Join - Output: share0_ref2.f1, share0_ref2.n, share0_ref1.f1, share0_ref1.n - Hash Cond: (share0_ref2.n = share0_ref1.n) - -> Redistribute Motion 3:3 (slice2; segments: 3) - Output: share0_ref2.f1, share0_ref2.n - Hash Key: share0_ref2.n - -> Shared Scan (share slice:id 2:0) - Output: share0_ref2.f1, share0_ref2.n - -> Hash + Output: share0_ref3.f1, share0_ref3.n, share0_ref2.f1, share0_ref2.n + -> Sequence + Output: share0_ref3.f1, share0_ref3.n, share0_ref2.f1, share0_ref2.n + -> Shared Scan (share slice:id 1:0) Output: share0_ref1.f1, share0_ref1.n - -> Redistribute Motion 3:3 (slice3; segments: 3) - Output: share0_ref1.f1, share0_ref1.n - Hash Key: share0_ref1.n - -> Shared Scan (share slice:id 3:0) - Output: share0_ref1.f1, share0_ref1.n - -> Seq Scan on public.subselect_tbl - Output: subselect_tbl.f1, 'regression'::name + -> Result + Output: subselect_tbl.f1, ('regression'::name) + Filter: (('regression'::name) = 'regression'::name) + -> Seq Scan on public.subselect_tbl + Output: 'regression'::name, subselect_tbl.f1 + -> Hash Join + Output: share0_ref3.f1, share0_ref3.n, share0_ref2.f1, share0_ref2.n + Hash Cond: (share0_ref3.n = share0_ref2.n) + -> Redistribute Motion 3:3 (slice2; segments: 3) + Output: share0_ref3.f1, share0_ref3.n + Hash Key: share0_ref3.n + -> Result + Output: share0_ref3.f1, share0_ref3.n + Filter: (share0_ref3.n = 'regression'::name) + -> Shared Scan (share slice:id 2:0) + Output: share0_ref3.f1, share0_ref3.n + -> Hash + Output: share0_ref2.f1, share0_ref2.n + -> Redistribute Motion 3:3 (slice3; segments: 3) + Output: share0_ref2.f1, share0_ref2.n + Hash Key: share0_ref2.n + -> Result + Output: share0_ref2.f1, share0_ref2.n + Filter: (share0_ref2.n = 'regression'::name) + -> Shared Scan (share slice:id 3:0) + Output: share0_ref2.f1, share0_ref2.n Settings: gp_cte_sharing=on, optimizer=on - Optimizer: Postgres query optimizer -(21 rows) +(34 rows) explain (verbose, costs off) with x as not materialized (select * from (select f1, current_database() as n from subselect_tbl) ss) select * from x, x x2 where x.n = x2.n; - QUERY PLAN --------------------------------------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) - Output: subselect_tbl.f1, ('regression'::name), subselect_tbl_1.f1, ('regression'::name) - -> Hash Join - Output: subselect_tbl.f1, ('regression'::name), subselect_tbl_1.f1, ('regression'::name) - Hash Cond: (('regression'::name) = ('regression'::name)) - -> Redistribute Motion 3:3 (slice2; segments: 3) - Output: subselect_tbl.f1, ('regression'::name) - Hash Key: ('regression'::name) - -> Seq Scan on public.subselect_tbl - Output: subselect_tbl.f1, 'regression'::name - -> Hash - Output: subselect_tbl_1.f1, ('regression'::name) - -> Redistribute Motion 3:3 (slice3; segments: 3) - Output: subselect_tbl_1.f1, ('regression'::name) - Hash Key: ('regression'::name) - -> Seq Scan on public.subselect_tbl subselect_tbl_1 - Output: subselect_tbl_1.f1, 'regression'::name - Optimizer: Postgres query optimizer + Output: share0_ref3.f1, share0_ref3.n, share0_ref2.f1, share0_ref2.n + -> Sequence + Output: share0_ref3.f1, share0_ref3.n, share0_ref2.f1, share0_ref2.n + -> Shared Scan (share slice:id 1:0) + Output: share0_ref1.f1, share0_ref1.n + -> Result + Output: subselect_tbl.f1, ('regression'::name) + Filter: (('regression'::name) = 'regression'::name) + -> Seq Scan on public.subselect_tbl + Output: 'regression'::name, subselect_tbl.f1 + -> Hash Join + Output: share0_ref3.f1, share0_ref3.n, share0_ref2.f1, share0_ref2.n + Hash Cond: (share0_ref3.n = share0_ref2.n) + -> Redistribute Motion 3:3 (slice2; segments: 3) + Output: share0_ref3.f1, share0_ref3.n + Hash Key: share0_ref3.n + -> Result + Output: share0_ref3.f1, share0_ref3.n + Filter: (share0_ref3.n = 'regression'::name) + -> Shared Scan (share slice:id 2:0) + Output: share0_ref3.f1, share0_ref3.n + -> Hash + Output: share0_ref2.f1, share0_ref2.n + -> Redistribute Motion 3:3 (slice3; segments: 3) + Output: share0_ref2.f1, share0_ref2.n + Hash Key: share0_ref2.n + -> Result + Output: share0_ref2.f1, share0_ref2.n + Filter: (share0_ref2.n = 'regression'::name) + -> Shared Scan (share slice:id 3:0) + Output: share0_ref2.f1, share0_ref2.n Settings: gp_cte_sharing=on, optimizer=on -(19 rows) +(34 rows) -- Multiply-referenced CTEs can't be inlined if they contain outer self-refs explain (verbose, costs off) diff --git a/src/test/regress/expected/union_gp_optimizer.out b/src/test/regress/expected/union_gp_optimizer.out index 8ff8655591d..bcfb05ec964 100644 --- a/src/test/regress/expected/union_gp_optimizer.out +++ b/src/test/regress/expected/union_gp_optimizer.out @@ -51,8 +51,6 @@ select 1 a, row_number() over (partition by 'a') union all (select 1 a , 2 b); -- This should preserve domain types select pg_typeof(a) from (select 'a'::information_schema.sql_identifier a union all select 'b'::information_schema.sql_identifier)a; -INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation pg_typeof ----------------------------------- information_schema.sql_identifier @@ -78,8 +76,6 @@ DETAIL: Falling back to Postgres-based planner because GPORCA does not support -- Yet, we keep behaviors on text-like columns select pg_typeof(a) from(select 'foo' a union select 'foo'::name)s; -INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation pg_typeof ----------- name @@ -87,8 +83,6 @@ DETAIL: Falling back to Postgres-based planner because GPORCA does not support select pg_typeof(a) from(select 1 x, 'foo' a union select 1, 'foo' union select 1, 'foo'::name)s; -INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation pg_typeof ----------- text @@ -96,8 +90,6 @@ DETAIL: Falling back to Postgres-based planner because GPORCA does not support select pg_typeof(a) from(select 1 x, 'foo' a union (select 1, 'foo' union select 1, 'foo'::name))s; -INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation pg_typeof ----------- name diff --git a/src/test/regress/expected/union_optimizer.out b/src/test/regress/expected/union_optimizer.out index 551709ba271..cbdcdf92ab5 100644 --- a/src/test/regress/expected/union_optimizer.out +++ b/src/test/regress/expected/union_optimizer.out @@ -1332,14 +1332,19 @@ explain (costs off) UNION ALL SELECT 2 AS t, * FROM tenk1 b) c WHERE t = 2; -INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner -DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature: Non-default collation - QUERY PLAN ------------------------------------------- + QUERY PLAN +---------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) - -> Seq Scan on tenk1 b - Optimizer: Postgres query optimizer -(3 rows) + -> Append + -> Result + One-Time Filter: (gp_execution_segment() = 0) + -> Result + One-Time Filter: false + -> Result + Filter: ((2) = 2) + -> Seq Scan on tenk1 b + Optimizer: GPORCA +(10 rows) -- Test that we push quals into UNION sub-selects only when it's safe explain (costs off) diff --git a/src/test/regress/expected/write_parallel_optimizer.out b/src/test/regress/expected/write_parallel_optimizer.out new file mode 100644 index 00000000000..dd754a6ca6c --- /dev/null +++ b/src/test/regress/expected/write_parallel_optimizer.out @@ -0,0 +1,86 @@ +-- +-- PARALLEL +-- +begin; +-- encourage use of parallel plans +set parallel_setup_cost=0; +set parallel_tuple_cost=0; +set min_parallel_table_scan_size=0; +set max_parallel_workers_per_gather=4; +-- +-- Test write operations that has an underlying query that is eligible +-- for parallel plans +-- +explain (costs off) create table parallel_write as + select length(stringu1) from tenk1 group by length(stringu1); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause. Creating a NULL policy entry. + QUERY PLAN +------------------------------------------------------------------ + Result + -> Redistribute Motion 3:3 (slice1; segments: 3) + -> HashAggregate + Group Key: (length((stringu1)::text)) + -> Redistribute Motion 3:3 (slice2; segments: 3) + Hash Key: (length((stringu1)::text)) + -> Seq Scan on tenk1 + Optimizer: GPORCA +(8 rows) + +create table parallel_write as + select length(stringu1) from tenk1 group by length(stringu1); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause. Creating a NULL policy entry. +drop table parallel_write; +explain (costs off) select length(stringu1) into parallel_write + from tenk1 group by length(stringu1); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause. Creating a NULL policy entry. + QUERY PLAN +------------------------------------------------------------------ + Result + -> Redistribute Motion 3:3 (slice1; segments: 3) + -> HashAggregate + Group Key: (length((stringu1)::text)) + -> Redistribute Motion 3:3 (slice2; segments: 3) + Hash Key: (length((stringu1)::text)) + -> Seq Scan on tenk1 + Optimizer: GPORCA +(8 rows) + +select length(stringu1) into parallel_write + from tenk1 group by length(stringu1); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause. Creating a NULL policy entry. +drop table parallel_write; +explain (costs off) create materialized view parallel_mat_view as + select length(stringu1) from tenk1 group by length(stringu1); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause. Creating a NULL policy entry. + QUERY PLAN +------------------------------------------------------------------ + Result + -> Redistribute Motion 3:3 (slice1; segments: 3) + -> HashAggregate + Group Key: (length((stringu1)::text)) + -> Redistribute Motion 3:3 (slice2; segments: 3) + Hash Key: (length((stringu1)::text)) + -> Seq Scan on tenk1 + Optimizer: GPORCA +(8 rows) + +create materialized view parallel_mat_view as + select length(stringu1) from tenk1 group by length(stringu1); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause. Creating a NULL policy entry. +create unique index on parallel_mat_view(length); +ERROR: UNIQUE and DISTRIBUTED RANDOMLY are incompatible +refresh materialized view parallel_mat_view; +ERROR: current transaction is aborted, commands ignored until end of transaction block +refresh materialized view concurrently parallel_mat_view; +ERROR: current transaction is aborted, commands ignored until end of transaction block +drop materialized view parallel_mat_view; +ERROR: current transaction is aborted, commands ignored until end of transaction block +prepare prep_stmt as select length(stringu1) from tenk1 group by length(stringu1); +ERROR: current transaction is aborted, commands ignored until end of transaction block +explain (costs off) create table parallel_write as execute prep_stmt; +ERROR: current transaction is aborted, commands ignored until end of transaction block +create table parallel_write as execute prep_stmt; +ERROR: current transaction is aborted, commands ignored until end of transaction block +drop table parallel_write; +ERROR: current transaction is aborted, commands ignored until end of transaction block +rollback; diff --git a/src/test/regress/init_file b/src/test/regress/init_file index 3220ef7bfa6..4b6a389c80c 100644 --- a/src/test/regress/init_file +++ b/src/test/regress/init_file @@ -23,6 +23,8 @@ m/^ Optimizer: GPORCA/ m/^ Optimizer: Postgres-based planner/ m/^ Settings:.*/ +m/^DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature:.*/ + # There are a number of NOTICE and HINT messages around table distribution, # for example to inform the user that the database will pick a particular # column in order to distribute the data. Merging tests from postgres will diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index b2ed818f677..8f7efaf499a 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -196,3 +196,4 @@ test: stats # test of tag test: tag +test: collation_orca \ No newline at end of file diff --git a/src/test/regress/sql/collation_orca.sql b/src/test/regress/sql/collation_orca.sql new file mode 100644 index 00000000000..502906201d5 --- /dev/null +++ b/src/test/regress/sql/collation_orca.sql @@ -0,0 +1,473 @@ +-- +-- Test ORCA optimizer handling of COLLATE "C" in en_US.UTF-8 database. +-- +-- This test verifies that ORCA correctly propagates column-level and +-- expression-level collation through its internal DXL representation, +-- producing plans with correct collation semantics. +-- +-- Prerequisites: database must have LC_COLLATE=en_US.UTF-8 (non-C locale) +-- so that C collation differs from the default. +-- + +-- Force ORCA and make fallback visible +SET optimizer TO on; +SET optimizer_trace_fallback TO on; + +CREATE SCHEMA collate_orca; +SET search_path = collate_orca; + +-- ====================================================================== +-- Setup: tables with COLLATE "C" columns +-- ====================================================================== + +CREATE TABLE t_c_collation ( + id int, + name text COLLATE "C", + val varchar(50) COLLATE "C" +) DISTRIBUTED BY (id); + +CREATE TABLE t_default_collation ( + id int, + name text, + val varchar(50) +) DISTRIBUTED BY (id); + +-- Mixed: some columns C, some default +CREATE TABLE t_mixed_collation ( + id int, + c_name text COLLATE "C", + d_name text +) DISTRIBUTED BY (id); + +-- Insert test data: uppercase letters have lower byte values than lowercase in ASCII +-- C collation: 'ABC' < 'abc' (byte order) +-- en_US.UTF-8: 'abc' < 'ABC' (case-insensitive primary sort) +INSERT INTO t_c_collation VALUES + (1, 'abc', 'apple'), + (2, 'ABC', 'APPLE'), + (3, 'def', 'banana'), + (4, 'DEF', 'BANANA'), + (5, 'ghi', 'cherry'), + (6, 'GHI', 'CHERRY'); + +INSERT INTO t_default_collation SELECT * FROM t_c_collation; +INSERT INTO t_mixed_collation SELECT id, name, name FROM t_c_collation; + +ANALYZE t_c_collation; +ANALYZE t_default_collation; +ANALYZE t_mixed_collation; + +-- ====================================================================== +-- Test 8.3: ORDER BY uses C collation, not en_US.UTF-8 +-- ====================================================================== + +-- C collation: uppercase before lowercase (byte order: A=65 < a=97) +-- If ORCA incorrectly uses default collation, order will be different +SELECT name FROM t_c_collation ORDER BY name; + +-- Compare with default collation table (should have different order) +SELECT name FROM t_default_collation ORDER BY name; + +-- Verify sort order is strictly byte-based +SELECT name, val FROM t_c_collation ORDER BY val; + +-- ORDER BY DESC +SELECT name FROM t_c_collation ORDER BY name DESC; + +-- Multi-column ORDER BY with C collation +SELECT name, val FROM t_c_collation ORDER BY name, val; + +-- ====================================================================== +-- Test 8.4: WHERE = comparison uses C collation +-- ====================================================================== + +-- Equality: case-sensitive under C collation +SELECT id, name FROM t_c_collation WHERE name = 'abc'; +SELECT id, name FROM t_c_collation WHERE name = 'ABC'; + +-- These should return different rows (C is case-sensitive) +SELECT count(*) FROM t_c_collation WHERE name = 'abc'; +SELECT count(*) FROM t_c_collation WHERE name = 'ABC'; + +-- Range comparison: under C, 'Z' < 'a' (byte order) +SELECT name FROM t_c_collation WHERE name < 'a' ORDER BY name; +SELECT name FROM t_c_collation WHERE name >= 'a' ORDER BY name; + +-- IN list with C collation +SELECT name FROM t_c_collation WHERE name IN ('abc', 'DEF') ORDER BY name; + +-- ====================================================================== +-- Test 8.5: JOIN on COLLATE "C" columns +-- ====================================================================== + +-- Inner join: should match on exact case under C collation +SELECT a.id, a.name, b.id, b.name +FROM t_c_collation a JOIN t_c_collation b ON a.name = b.name +WHERE a.id < b.id +ORDER BY a.name, a.id; + +-- Join between C-collation and default-collation tables +-- The join should still work (both sides produce same text) +SELECT c.id, c.name, d.id, d.name +FROM t_c_collation c JOIN t_default_collation d ON c.id = d.id +WHERE c.name = d.name +ORDER BY c.name; + +-- Self-join with inequality (tests collation in merge/hash join) +SELECT a.name, b.name +FROM t_c_collation a JOIN t_c_collation b ON a.name < b.name +WHERE a.id = 1 AND b.id IN (2, 4, 6) +ORDER BY a.name, b.name; + +-- ====================================================================== +-- Test 8.6: GROUP BY on COLLATE "C" column +-- ====================================================================== + +-- Under C collation, 'abc' and 'ABC' are different groups +SELECT name, count(*) FROM t_c_collation GROUP BY name ORDER BY name; + +-- Aggregate with C collation grouping +SELECT name, min(val), max(val) +FROM t_c_collation GROUP BY name ORDER BY name; + +-- GROUP BY on expression involving C collation column +SELECT upper(name) as uname, count(*) +FROM t_c_collation GROUP BY upper(name) ORDER BY uname; + +-- HAVING with C collation +SELECT name, count(*) as cnt +FROM t_c_collation GROUP BY name HAVING name > 'Z' ORDER BY name; + +-- ====================================================================== +-- Test 8.7: Window functions with COLLATE "C" PARTITION BY +-- ====================================================================== + +-- Partition by C-collation column +SELECT name, val, + row_number() OVER (PARTITION BY name ORDER BY val) as rn +FROM t_c_collation +ORDER BY name, val; + +-- Window with ORDER BY on C-collation column +SELECT name, val, + rank() OVER (ORDER BY name) as rnk +FROM t_c_collation +ORDER BY name, val; + +-- Multiple window functions +SELECT name, val, + count(*) OVER (PARTITION BY name) as grp_cnt, + first_value(val) OVER (PARTITION BY name ORDER BY val) as first_val +FROM t_c_collation +ORDER BY name, val; + +-- Window with expression-level COLLATE "C" on default-collation table +SELECT name, val, + row_number() OVER (PARTITION BY name COLLATE "C" ORDER BY val COLLATE "C") as rn +FROM t_default_collation +ORDER BY name COLLATE "C", val COLLATE "C"; + +-- ====================================================================== +-- Test 8.8: EXPLAIN shows correct collation in plan +-- ====================================================================== + +-- The sort key should reflect C collation, not default +EXPLAIN (COSTS OFF) SELECT name FROM t_c_collation ORDER BY name; + +-- Join plan should use correct collation +EXPLAIN (COSTS OFF) +SELECT a.name, b.name +FROM t_c_collation a JOIN t_c_collation b ON a.name = b.name +ORDER BY a.name; + +-- Aggregate plan +EXPLAIN (COSTS OFF) +SELECT name, count(*) FROM t_c_collation GROUP BY name ORDER BY name; + +-- ====================================================================== +-- Test 8.9: Mixed default + C collation columns in same query +-- ====================================================================== + +-- Query referencing both C and default collation columns +SELECT c_name, d_name +FROM t_mixed_collation +ORDER BY c_name; + +SELECT c_name, d_name +FROM t_mixed_collation +ORDER BY d_name; + +-- Mixed columns in WHERE +SELECT id, c_name, d_name +FROM t_mixed_collation +WHERE c_name = 'abc' AND d_name = 'abc'; + +-- Mixed columns in GROUP BY +SELECT c_name, d_name, count(*) +FROM t_mixed_collation +GROUP BY c_name, d_name +ORDER BY c_name, d_name; + +-- Join on C column, filter on default column +SELECT m.id, m.c_name, m.d_name +FROM t_mixed_collation m JOIN t_c_collation c ON m.c_name = c.name +WHERE m.d_name > 'D' +ORDER BY m.c_name; + +-- ====================================================================== +-- Test: Collation resolution for mixed-collation argument lists +-- gpdb::ExprCollation(List) must match PG's merge_collation_state() rule: +-- non-default implicit collation always beats default, regardless of +-- argument order. Previously the translator just returned the first +-- non-InvalidOid collation, so (default, C) picked default — wrong. +-- ====================================================================== + +-- coalesce: DEFAULT column first, C column second +-- Must sort in C byte order (A < B < a < b), not locale order (a < A < b < B) +SELECT coalesce(d_name, c_name) AS r FROM t_mixed_collation ORDER BY coalesce(d_name, c_name); + +-- coalesce: C column first, DEFAULT column second (control — always worked) +SELECT coalesce(c_name, d_name) AS r FROM t_mixed_collation ORDER BY coalesce(c_name, d_name); + +-- Verify both orders produce identical results +SELECT coalesce(d_name, c_name) AS dc, coalesce(c_name, d_name) AS cd +FROM t_mixed_collation +ORDER BY coalesce(d_name, c_name); + +-- EXPLAIN must show COLLATE "C" on the sort key regardless of arg order +EXPLAIN (COSTS OFF) +SELECT * FROM t_mixed_collation ORDER BY coalesce(d_name, c_name); + +-- Operator expression: 'literal' || c_col (DEFAULT || C → should pick C) +SELECT d_name || c_name AS r FROM t_mixed_collation ORDER BY d_name || c_name; + +-- min/max with mixed-collation coalesce argument +SELECT min(coalesce(d_name, c_name)), max(coalesce(d_name, c_name)) FROM t_mixed_collation; + +-- CASE result with mixed collation branches +-- WHEN branch returns d_name (default), ELSE returns c_name (C). +-- Output collation should be C. +SELECT CASE WHEN id <= 3 THEN d_name ELSE c_name END AS r +FROM t_mixed_collation +ORDER BY CASE WHEN id <= 3 THEN d_name ELSE c_name END; + +-- ====================================================================== +-- Test: Expression-level COLLATE "C" +-- ====================================================================== + +-- COLLATE in WHERE clause on default-collation table +SELECT name FROM t_default_collation WHERE name COLLATE "C" < 'a' ORDER BY name COLLATE "C"; + +-- COLLATE in ORDER BY on default-collation table +SELECT name FROM t_default_collation ORDER BY name COLLATE "C"; + +-- COLLATE in expression +SELECT name, name COLLATE "C" < 'a' as is_upper +FROM t_default_collation ORDER BY name COLLATE "C"; + +-- ====================================================================== +-- Test: Subqueries and CTEs with C collation +-- ====================================================================== + +-- Subquery preserves C collation +SELECT * FROM ( + SELECT name, val FROM t_c_collation ORDER BY name +) sub +ORDER BY name; + +-- CTE with C collation +WITH ranked AS ( + SELECT name, val, row_number() OVER (ORDER BY name) as rn + FROM t_c_collation +) +SELECT * FROM ranked ORDER BY rn; + +-- Correlated subquery +SELECT c.name, c.val +FROM t_c_collation c +WHERE c.name = (SELECT min(name) FROM t_c_collation WHERE val = c.val) +ORDER BY c.name; + +-- ====================================================================== +-- Test: UNION / INTERSECT / EXCEPT with C collation +-- ====================================================================== + +SELECT name FROM t_c_collation WHERE name < 'a' +UNION ALL +SELECT name FROM t_c_collation WHERE name >= 'a' +ORDER BY name; + +SELECT name FROM t_c_collation +INTERSECT +SELECT name FROM t_default_collation +ORDER BY name; + +-- ====================================================================== +-- Test: DISTINCT C collation column +-- ====================================================================== + +-- Under C collation, 'abc' and 'ABC' are distinct +SELECT DISTINCT name FROM t_c_collation ORDER BY name; + +-- ====================================================================== +-- Test: String functions with C collation +-- ====================================================================== + +SELECT name, length(name), upper(name), lower(name) +FROM t_c_collation ORDER BY name; + +-- min/max aggregate should respect C collation +SELECT min(name), max(name) FROM t_c_collation; + +-- string_agg with ORDER BY using C collation +SELECT string_agg(name, ',' ORDER BY name) FROM t_c_collation; + +-- ====================================================================== +-- Test: LIKE / pattern matching with C collation +-- ====================================================================== + +-- LIKE is byte-based under C collation +SELECT name FROM t_c_collation WHERE name LIKE 'a%' ORDER BY name; +SELECT name FROM t_c_collation WHERE name LIKE 'A%' ORDER BY name; + +-- BETWEEN uses C collation ordering +-- Under C: 'D' < 'Z' < 'a', so BETWEEN 'A' AND 'Z' gets only uppercase +SELECT name FROM t_c_collation WHERE name BETWEEN 'A' AND 'Z' ORDER BY name; + +-- ====================================================================== +-- Test: Index scan with C collation +-- ====================================================================== + +CREATE INDEX idx_c_name ON t_c_collation (name); +ANALYZE t_c_collation; + +-- Index scan should respect C collation ordering +SET enable_seqscan TO off; +SELECT name FROM t_c_collation WHERE name > 'Z' ORDER BY name; +SELECT name FROM t_c_collation WHERE name <= 'Z' ORDER BY name; +RESET enable_seqscan; + +DROP INDEX idx_c_name; + +-- ====================================================================== +-- Test: CASE expression with C collation comparison +-- ====================================================================== + +SELECT name, + CASE WHEN name < 'a' THEN 'uppercase' ELSE 'lowercase' END as case_type +FROM t_c_collation +ORDER BY name; + +-- ====================================================================== +-- Test: Aggregate functions with C collation +-- ====================================================================== + +-- count with GROUP BY preserves C collation grouping +SELECT name, count(*), sum(id) +FROM t_c_collation GROUP BY name ORDER BY name; + +-- array_agg with ORDER BY should use C collation +SELECT array_agg(name ORDER BY name) FROM t_c_collation; + +-- min/max on varchar(50) COLLATE "C" column +SELECT min(val), max(val) FROM t_c_collation; + +-- ====================================================================== +-- Test: LIMIT / OFFSET with C collation ORDER BY +-- ====================================================================== + +SELECT name FROM t_c_collation ORDER BY name LIMIT 3; +SELECT name FROM t_c_collation ORDER BY name LIMIT 3 OFFSET 3; + +-- ====================================================================== +-- Test: EXCEPT with C collation +-- ====================================================================== + +-- All uppercase names (< 'a' under C) except DEF +SELECT name FROM t_c_collation WHERE name < 'a' +EXCEPT +SELECT name FROM t_c_collation WHERE name = 'DEF' +ORDER BY name; + +-- ====================================================================== +-- Test: INSERT INTO ... SELECT preserves C collation +-- ====================================================================== + +CREATE TABLE t_c_copy (id int, name text COLLATE "C") DISTRIBUTED BY (id); +INSERT INTO t_c_copy SELECT id, name FROM t_c_collation; +SELECT name FROM t_c_copy ORDER BY name; +DROP TABLE t_c_copy; + +-- ====================================================================== +-- Test: CTAS with C collation column +-- ====================================================================== + +CREATE TABLE t_c_ctas AS SELECT id, name FROM t_c_collation DISTRIBUTED BY (id); +-- Verify the new table inherits C collation +SELECT name FROM t_c_ctas ORDER BY name; +DROP TABLE t_c_ctas; + +-- ====================================================================== +-- Test: Multiple aggregates in same query +-- ====================================================================== + +SELECT min(name), max(name), min(val), max(val), + count(DISTINCT name) +FROM t_c_collation; + +-- ====================================================================== +-- Test: Window functions with C collation ordering +-- ====================================================================== + +-- lag/lead should follow C collation order +SELECT name, + lag(name) OVER (ORDER BY name) as prev_name, + lead(name) OVER (ORDER BY name) as next_name +FROM t_c_collation +ORDER BY name; + +-- ntile with C collation partitioning +SELECT name, + ntile(2) OVER (ORDER BY name) as bucket +FROM t_c_collation +ORDER BY name; + +-- ====================================================================== +-- Test: Nested subquery with C collation +-- ====================================================================== + +SELECT name FROM t_c_collation +WHERE name IN (SELECT name FROM t_c_collation WHERE name < 'a') +ORDER BY name; + +-- Scalar subquery with min/max on C collation +SELECT name, + (SELECT min(b.name) FROM t_c_collation b WHERE b.name > a.name) as next_min +FROM t_c_collation a +ORDER BY name; + +-- ====================================================================== +-- Test: UPDATE/DELETE with C collation WHERE clause +-- ====================================================================== + +CREATE TABLE t_c_dml (id int, name text COLLATE "C") DISTRIBUTED BY (id); +INSERT INTO t_c_dml SELECT id, name FROM t_c_collation; + +-- DELETE rows where name < 'a' (uppercase under C collation) +DELETE FROM t_c_dml WHERE name < 'a'; +SELECT name FROM t_c_dml ORDER BY name; + +-- Re-insert and UPDATE +INSERT INTO t_c_dml SELECT id, name FROM t_c_collation WHERE name < 'a'; +UPDATE t_c_dml SET name = name || '_updated' WHERE name < 'a'; +SELECT name FROM t_c_dml ORDER BY name; + +DROP TABLE t_c_dml; + +-- ====================================================================== +-- Cleanup +-- ====================================================================== + +RESET optimizer_trace_fallback; +RESET optimizer; +DROP SCHEMA collate_orca CASCADE; From 6dc30c2929a7fb2f12329110e966dcf02fd4c7f5 Mon Sep 17 00:00:00 2001 From: Jianghua Yang Date: Tue, 31 Mar 2026 03:05:15 +0800 Subject: [PATCH 3/6] fix pax regress --- .../expected/btree_index_optimizer.out | 445 ++++++++++++ .../regress/expected/cluster_optimizer.out | 648 ++++++++++++++++++ .../regress/expected/equivclass_optimizer.out | 18 +- .../expected/gp_array_agg_optimizer.out | 19 +- .../expected/rowsecurity_optimizer.out | 35 +- .../expected/select_views_optimizer.out | 48 +- .../regress/expected/stats_ext_optimizer.out | 4 +- .../regress/expected/subselect_optimizer.out | 234 ++++--- .../test/regress/expected/union_optimizer.out | 17 +- .../pax_storage/src/test/regress/init_file | 2 + 10 files changed, 1302 insertions(+), 168 deletions(-) create mode 100644 contrib/pax_storage/src/test/regress/expected/btree_index_optimizer.out create mode 100644 contrib/pax_storage/src/test/regress/expected/cluster_optimizer.out diff --git a/contrib/pax_storage/src/test/regress/expected/btree_index_optimizer.out b/contrib/pax_storage/src/test/regress/expected/btree_index_optimizer.out new file mode 100644 index 00000000000..d2fb80bcde9 --- /dev/null +++ b/contrib/pax_storage/src/test/regress/expected/btree_index_optimizer.out @@ -0,0 +1,445 @@ +-- +-- BTREE_INDEX +-- test retrieval of min/max keys for each index +-- +SELECT b.* + FROM bt_i4_heap b + WHERE b.seqno < 1; + seqno | random +-------+------------ + 0 | 1935401906 +(1 row) + +SELECT b.* + FROM bt_i4_heap b + WHERE b.seqno >= 9999; + seqno | random +-------+------------ + 9999 | 1227676208 +(1 row) + +SELECT b.* + FROM bt_i4_heap b + WHERE b.seqno = 4500; + seqno | random +-------+------------ + 4500 | 2080851358 +(1 row) + +SELECT b.* + FROM bt_name_heap b + WHERE b.seqno < '1'::name; + seqno | random +-------+------------ + 0 | 1935401906 +(1 row) + +SELECT b.* + FROM bt_name_heap b + WHERE b.seqno >= '9999'::name; + seqno | random +-------+------------ + 9999 | 1227676208 +(1 row) + +SELECT b.* + FROM bt_name_heap b + WHERE b.seqno = '4500'::name; + seqno | random +-------+------------ + 4500 | 2080851358 +(1 row) + +SELECT b.* + FROM bt_txt_heap b + WHERE b.seqno < '1'::text; + seqno | random +-------+------------ + 0 | 1935401906 +(1 row) + +SELECT b.* + FROM bt_txt_heap b + WHERE b.seqno >= '9999'::text; + seqno | random +-------+------------ + 9999 | 1227676208 +(1 row) + +SELECT b.* + FROM bt_txt_heap b + WHERE b.seqno = '4500'::text; + seqno | random +-------+------------ + 4500 | 2080851358 +(1 row) + +SELECT b.* + FROM bt_f8_heap b + WHERE b.seqno < '1'::float8; + seqno | random +-------+------------ + 0 | 1935401906 +(1 row) + +SELECT b.* + FROM bt_f8_heap b + WHERE b.seqno >= '9999'::float8; + seqno | random +-------+------------ + 9999 | 1227676208 +(1 row) + +SELECT b.* + FROM bt_f8_heap b + WHERE b.seqno = '4500'::float8; + seqno | random +-------+------------ + 4500 | 2080851358 +(1 row) + +-- +-- Check correct optimization of LIKE (special index operator support) +-- for both indexscan and bitmapscan cases +-- +set enable_seqscan to false; +set enable_indexscan to true; +set enable_bitmapscan to false; +set enable_sort to false; -- GPDB needs more strong-arming to get same plans as upstream +explain (costs off) +select proname from pg_proc where proname like E'RI\\_FKey%del' order by 1; + QUERY PLAN +------------------------------------------------------------------------------ + Index Only Scan using pg_proc_proname_args_nsp_index on pg_proc + Index Cond: ((proname >= 'RI_FKey'::text) AND (proname < 'RI_FKez'::text)) + Filter: (proname ~~ 'RI\_FKey%del'::text) + Optimizer: Postgres query optimizer +(4 rows) + +select proname from pg_proc where proname like E'RI\\_FKey%del' order by 1; + proname +------------------------ + RI_FKey_cascade_del + RI_FKey_noaction_del + RI_FKey_restrict_del + RI_FKey_setdefault_del + RI_FKey_setnull_del +(5 rows) + +explain (costs off) +select proname from pg_proc where proname ilike '00%foo' order by 1; + QUERY PLAN +-------------------------------------------------------------------- + Index Only Scan using pg_proc_proname_args_nsp_index on pg_proc + Index Cond: ((proname >= '00'::text) AND (proname < '01'::text)) + Filter: (proname ~~* '00%foo'::text) + Optimizer: Postgres query optimizer +(4 rows) + +select proname from pg_proc where proname ilike '00%foo' order by 1; + proname +--------- +(0 rows) + +explain (costs off) +select proname from pg_proc where proname ilike 'ri%foo' order by 1; + QUERY PLAN +----------------------------------------------------------------- + Index Only Scan using pg_proc_proname_args_nsp_index on pg_proc + Filter: (proname ~~* 'ri%foo'::text) + Optimizer: Postgres query optimizer +(3 rows) + +set enable_indexscan to false; +set enable_bitmapscan to true; +reset enable_sort; +explain (costs off) +select proname from pg_proc where proname like E'RI\\_FKey%del' order by 1; + QUERY PLAN +------------------------------------------------------------------------------------------ + Sort + Sort Key: proname + -> Bitmap Heap Scan on pg_proc + Filter: (proname ~~ 'RI\_FKey%del'::text) + -> Bitmap Index Scan on pg_proc_proname_args_nsp_index + Index Cond: ((proname >= 'RI_FKey'::text) AND (proname < 'RI_FKez'::text)) + Optimizer: Postgres query optimizer +(7 rows) + +select proname from pg_proc where proname like E'RI\\_FKey%del' order by 1; + proname +------------------------ + RI_FKey_cascade_del + RI_FKey_noaction_del + RI_FKey_restrict_del + RI_FKey_setdefault_del + RI_FKey_setnull_del +(5 rows) + +explain (costs off) +select proname from pg_proc where proname ilike '00%foo' order by 1; + QUERY PLAN +-------------------------------------------------------------------------------- + Sort + Sort Key: proname + -> Bitmap Heap Scan on pg_proc + Filter: (proname ~~* '00%foo'::text) + -> Bitmap Index Scan on pg_proc_proname_args_nsp_index + Index Cond: ((proname >= '00'::text) AND (proname < '01'::text)) + Optimizer: Postgres query optimizer +(7 rows) + +select proname from pg_proc where proname ilike '00%foo' order by 1; + proname +--------- +(0 rows) + +set enable_sort to false; -- GPDB needs more strong-arming to get same plans as upstream +set enable_bitmapscan to false; +explain (costs off) +select proname from pg_proc where proname ilike 'ri%foo' order by 1; + QUERY PLAN +----------------------------------------------------------------- + Index Only Scan using pg_proc_proname_args_nsp_index on pg_proc + Filter: (proname ~~* 'ri%foo'::text) + Optimizer: Postgres query optimizer +(3 rows) + +reset enable_seqscan; +reset enable_indexscan; +reset enable_bitmapscan; +reset enable_sort; +-- Also check LIKE optimization with binary-compatible cases +create temp table btree_bpchar (f1 text collate "C"); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'f1' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. +create index on btree_bpchar(f1 bpchar_ops) WITH (deduplicate_items=on); +insert into btree_bpchar values ('foo'), ('fool'), ('bar'), ('quux'); +-- doesn't match index: +explain (costs off) +select * from btree_bpchar where f1 like 'foo'; + QUERY PLAN +------------------------------------------ + Gather Motion 3:1 (slice1; segments: 3) + -> Seq Scan on btree_bpchar + Filter: (f1 ~~ 'foo'::text) + Optimizer: GPORCA +(4 rows) + +select * from btree_bpchar where f1 like 'foo'; + f1 +----- + foo +(1 row) + +explain (costs off) +select * from btree_bpchar where f1 like 'foo%'; + QUERY PLAN +------------------------------------------ + Gather Motion 3:1 (slice1; segments: 3) + -> Seq Scan on btree_bpchar + Filter: (f1 ~~ 'foo%'::text) + Optimizer: GPORCA +(4 rows) + +select * from btree_bpchar where f1 like 'foo%'; + f1 +------ + foo + fool +(2 rows) + +-- these do match the index: +explain (costs off) +select * from btree_bpchar where f1::bpchar like 'foo'; + QUERY PLAN +----------------------------------------------- + Gather Motion 3:1 (slice1; segments: 3) + -> Seq Scan on btree_bpchar + Filter: ((f1)::bpchar ~~ 'foo'::text) + Optimizer: GPORCA +(4 rows) + +select * from btree_bpchar where f1::bpchar like 'foo'; + f1 +----- + foo +(1 row) + +explain (costs off) +select * from btree_bpchar where f1::bpchar like 'foo%'; + QUERY PLAN +------------------------------------------------ + Gather Motion 3:1 (slice1; segments: 3) + -> Seq Scan on btree_bpchar + Filter: ((f1)::bpchar ~~ 'foo%'::text) + Optimizer: GPORCA +(4 rows) + +select * from btree_bpchar where f1::bpchar like 'foo%'; + f1 +------ + foo + fool +(2 rows) + +-- get test coverage for "single value" deduplication strategy: +insert into btree_bpchar select 'foo' from generate_series(1,1500); +-- +-- Perform unique checking, with and without the use of deduplication +-- +CREATE TABLE dedup_unique_test_table (a int); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. +CREATE UNIQUE INDEX dedup_unique ON dedup_unique_test_table (a) WITH (deduplicate_items=on); +CREATE UNIQUE INDEX plain_unique ON dedup_unique_test_table (a) WITH (deduplicate_items=off); +-- Generate enough garbage tuples in index to ensure that even the unique index +-- with deduplication enabled has to check multiple leaf pages during unique +-- checking (at least with a BLCKSZ of 8192 or less) +DO $$ +BEGIN + FOR r IN 1..50 LOOP + DELETE FROM dedup_unique_test_table; + INSERT INTO dedup_unique_test_table SELECT 1; + END LOOP; +END$$; +-- Exercise the LP_DEAD-bit-set tuple deletion code with a posting list tuple. +-- The implementation prefers deleting existing items to merging any duplicate +-- tuples into a posting list, so we need an explicit test to make sure we get +-- coverage (note that this test also assumes BLCKSZ is 8192 or less): +DROP INDEX plain_unique; +DELETE FROM dedup_unique_test_table WHERE a = 1; +INSERT INTO dedup_unique_test_table SELECT i FROM generate_series(0,450) i; +-- +-- Test B-tree fast path (cache rightmost leaf page) optimization. +-- +-- First create a tree that's at least three levels deep (i.e. has one level +-- between the root and leaf levels). The text inserted is long. It won't be +-- TOAST compressed because we use plain storage in the table. Only a few +-- index tuples fit on each internal page, allowing us to get a tall tree with +-- few pages. (A tall tree is required to trigger caching.) +-- +-- The text column must be the leading column in the index, since suffix +-- truncation would otherwise truncate tuples on internal pages, leaving us +-- with a short tree. +create table btree_tall_tbl(id int4, t text); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'id' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. +alter table btree_tall_tbl alter COLUMN t set storage plain; +create index btree_tall_idx on btree_tall_tbl (t, id) with (fillfactor = 10); +insert into btree_tall_tbl select g, repeat('x', 250) +from generate_series(1, 130) g; +-- +-- Test for multilevel page deletion +-- +CREATE TABLE delete_test_table (a bigint, b bigint, c bigint, d bigint); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. +INSERT INTO delete_test_table SELECT i, 1, 2, 3 FROM generate_series(1,80000) i; +ALTER TABLE delete_test_table ADD PRIMARY KEY (a,b,c,d); +-- Delete most entries, and vacuum, deleting internal pages and creating "fast +-- root" +DELETE FROM delete_test_table WHERE a < 79990; +VACUUM delete_test_table; +-- +-- Test B-tree insertion with a metapage update (XLOG_BTREE_INSERT_META +-- WAL record type). This happens when a "fast root" page is split. This +-- also creates coverage for nbtree FSM page recycling. +-- +-- The vacuum above should've turned the leaf page into a fast root. We just +-- need to insert some rows to cause the fast root page to split. +-- Pax not support IndexDeleteTuples +-- INSERT INTO delete_test_table SELECT i, 1, 2, 3 FROM generate_series(1,1000) i; +-- +-- GPDB: Test correctness of B-tree stats in consecutively VACUUM. +-- +CREATE TABLE btree_stats_tbl(col_int int, col_text text, col_numeric numeric, col_unq int) DISTRIBUTED BY (col_int); +CREATE INDEX btree_stats_idx ON btree_stats_tbl(col_int); +INSERT INTO btree_stats_tbl VALUES (1, 'aa', 1001, 101), (2, 'bb', 1002, 102); +SELECT reltuples FROM pg_class WHERE relname='btree_stats_tbl'; + reltuples +----------- + -1 +(1 row) + +-- inspect the state of the stats on segments +SELECT gp_segment_id, relname, reltuples FROM gp_dist_random('pg_class') WHERE relname = 'btree_stats_idx'; + gp_segment_id | relname | reltuples +---------------+-----------------+----------- + 1 | btree_stats_idx | 0 + 0 | btree_stats_idx | 0 + 2 | btree_stats_idx | 0 +(3 rows) + +SELECT reltuples FROM pg_class WHERE relname='btree_stats_idx'; + reltuples +----------- + 0 +(1 row) + +-- 1st ANALYZE, expect reltuples = 2 +-- Pax not support VACUUM yet, replace VACUUM with ANALYZE +ANALYZE btree_stats_tbl; +SELECT reltuples FROM pg_class WHERE relname='btree_stats_tbl'; + reltuples +----------- + 2 +(1 row) + +-- inspect the state of the stats on segments +SELECT gp_segment_id, relname, reltuples FROM gp_dist_random('pg_class') WHERE relname = 'btree_stats_idx'; + gp_segment_id | relname | reltuples +---------------+-----------------+----------- + 0 | btree_stats_idx | 1 + 2 | btree_stats_idx | 0 + 1 | btree_stats_idx | 1 +(3 rows) + +SELECT reltuples FROM pg_class WHERE relname='btree_stats_idx'; + reltuples +----------- + 2 +(1 row) + +-- 2nd ANALYZE, expect reltuples = 2 +ANALYZE btree_stats_tbl; +SELECT reltuples FROM pg_class WHERE relname='btree_stats_tbl'; + reltuples +----------- + 2 +(1 row) + +-- inspect the state of the stats on segments +SELECT gp_segment_id, relname, reltuples FROM gp_dist_random('pg_class') WHERE relname = 'btree_stats_idx'; + gp_segment_id | relname | reltuples +---------------+-----------------+----------- + 0 | btree_stats_idx | 1 + 2 | btree_stats_idx | 0 + 1 | btree_stats_idx | 1 +(3 rows) + +SELECT reltuples FROM pg_class WHERE relname='btree_stats_idx'; + reltuples +----------- + 2 +(1 row) + +-- Prior to this fix, the case would be failed here. Given the +-- scenario of updating stats during VACUUM: +-- 1) coordinator vacuums and updates stats of its own; +-- 2) then coordinator dispatches vacuum to segments; +-- 3) coordinator combines stats received from segments to overwrite the stats of its own. +-- Because upstream introduced a feature which could skip full index scan uring cleanup +-- of B-tree indexes when possible (refer to: +-- https://github.com/postgres/postgres/commit/857f9c36cda520030381bd8c2af20adf0ce0e1d4), +-- there was a case in QD-QEs distributed deployment that some QEs could skip full index scan and +-- stop updating statistics, result in QD being unable to collect all QEs' stats thus overwrote +-- a paritial accumulated value to index->reltuples. More interesting, it usually happened starting +-- from the 3rd time of consecutively VACUUM after fresh inserts due to above skipping index scan +-- criteria. +-- 3rd VACUUM, expect reltuples = 2 +-- VACUUM btree_stats_tbl; +-- SELECT reltuples FROM pg_class WHERE relname='btree_stats_tbl'; +-- inspect the state of the stats on segments +-- SELECT gp_segment_id, relname, reltuples FROM gp_dist_random('pg_class') WHERE relname = 'btree_stats_idx'; +-- SELECT reltuples FROM pg_class WHERE relname='btree_stats_idx'; diff --git a/contrib/pax_storage/src/test/regress/expected/cluster_optimizer.out b/contrib/pax_storage/src/test/regress/expected/cluster_optimizer.out new file mode 100644 index 00000000000..2a87c5355c2 --- /dev/null +++ b/contrib/pax_storage/src/test/regress/expected/cluster_optimizer.out @@ -0,0 +1,648 @@ +-- +-- CLUSTER +-- +CREATE TABLE clstr_tst_s (rf_a SERIAL PRIMARY KEY, + b INT) DISTRIBUTED BY (rf_a); +CREATE TABLE clstr_tst (a SERIAL PRIMARY KEY, + b INT, + c TEXT, + d TEXT, + CONSTRAINT clstr_tst_con FOREIGN KEY (b) REFERENCES clstr_tst_s) + DISTRIBUTED BY (a); +WARNING: referential integrity (FOREIGN KEY) constraints are not supported in Apache Cloudberry, will not be enforced +CREATE INDEX clstr_tst_b ON clstr_tst (b); +CREATE INDEX clstr_tst_c ON clstr_tst (c); +CREATE INDEX clstr_tst_c_b ON clstr_tst (c,b); +CREATE INDEX clstr_tst_b_c ON clstr_tst (b,c); +INSERT INTO clstr_tst_s (b) VALUES (0); +INSERT INTO clstr_tst_s (b) SELECT b FROM clstr_tst_s; +INSERT INTO clstr_tst_s (b) SELECT b FROM clstr_tst_s; +INSERT INTO clstr_tst_s (b) SELECT b FROM clstr_tst_s; +INSERT INTO clstr_tst_s (b) SELECT b FROM clstr_tst_s; +INSERT INTO clstr_tst_s (b) SELECT b FROM clstr_tst_s; +CREATE TABLE clstr_tst_inh () INHERITS (clstr_tst); +NOTICE: table has parent, setting distribution columns to match parent table +INSERT INTO clstr_tst (b, c) VALUES (11, 'once'); +INSERT INTO clstr_tst (b, c) VALUES (10, 'diez'); +INSERT INTO clstr_tst (b, c) VALUES (31, 'treinta y uno'); +INSERT INTO clstr_tst (b, c) VALUES (22, 'veintidos'); +INSERT INTO clstr_tst (b, c) VALUES (3, 'tres'); +INSERT INTO clstr_tst (b, c) VALUES (20, 'veinte'); +INSERT INTO clstr_tst (b, c) VALUES (23, 'veintitres'); +INSERT INTO clstr_tst (b, c) VALUES (21, 'veintiuno'); +INSERT INTO clstr_tst (b, c) VALUES (4, 'cuatro'); +INSERT INTO clstr_tst (b, c) VALUES (14, 'catorce'); +INSERT INTO clstr_tst (b, c) VALUES (2, 'dos'); +INSERT INTO clstr_tst (b, c) VALUES (18, 'dieciocho'); +INSERT INTO clstr_tst (b, c) VALUES (27, 'veintisiete'); +INSERT INTO clstr_tst (b, c) VALUES (25, 'veinticinco'); +INSERT INTO clstr_tst (b, c) VALUES (13, 'trece'); +INSERT INTO clstr_tst (b, c) VALUES (28, 'veintiocho'); +INSERT INTO clstr_tst (b, c) VALUES (32, 'treinta y dos'); +INSERT INTO clstr_tst (b, c) VALUES (5, 'cinco'); +INSERT INTO clstr_tst (b, c) VALUES (29, 'veintinueve'); +INSERT INTO clstr_tst (b, c) VALUES (1, 'uno'); +INSERT INTO clstr_tst (b, c) VALUES (24, 'veinticuatro'); +INSERT INTO clstr_tst (b, c) VALUES (30, 'treinta'); +INSERT INTO clstr_tst (b, c) VALUES (12, 'doce'); +INSERT INTO clstr_tst (b, c) VALUES (17, 'diecisiete'); +INSERT INTO clstr_tst (b, c) VALUES (9, 'nueve'); +INSERT INTO clstr_tst (b, c) VALUES (19, 'diecinueve'); +INSERT INTO clstr_tst (b, c) VALUES (26, 'veintiseis'); +INSERT INTO clstr_tst (b, c) VALUES (15, 'quince'); +INSERT INTO clstr_tst (b, c) VALUES (7, 'siete'); +INSERT INTO clstr_tst (b, c) VALUES (16, 'dieciseis'); +INSERT INTO clstr_tst (b, c) VALUES (8, 'ocho'); +-- This entry is needed to test that TOASTED values are copied correctly. +INSERT INTO clstr_tst (b, c, d) VALUES (6, 'seis', repeat('xyzzy', 100000)); +CLUSTER clstr_tst_c ON clstr_tst; +SELECT a,b,c,substring(d for 30), length(d) from clstr_tst; + a | b | c | substring | length +----+----+---------------+--------------------------------+-------- + 26 | 19 | diecinueve | | + 12 | 18 | dieciocho | | + 30 | 16 | dieciseis | | + 23 | 12 | doce | | + 31 | 8 | ocho | | + 1 | 11 | once | | + 15 | 13 | trece | | + 20 | 1 | uno | | + 18 | 5 | cinco | | + 24 | 17 | diecisiete | | + 2 | 10 | diez | | + 29 | 7 | siete | | + 22 | 30 | treinta | | + 3 | 31 | treinta y uno | | + 4 | 22 | veintidos | | + 19 | 29 | veintinueve | | + 16 | 28 | veintiocho | | + 27 | 26 | veintiseis | | + 7 | 23 | veintitres | | + 8 | 21 | veintiuno | | + 10 | 14 | catorce | | + 9 | 4 | cuatro | | + 11 | 2 | dos | | + 25 | 9 | nueve | | + 28 | 15 | quince | | + 32 | 6 | seis | xyzzyxyzzyxyzzyxyzzyxyzzyxyzzy | 500000 + 17 | 32 | treinta y dos | | + 5 | 3 | tres | | + 6 | 20 | veinte | | + 14 | 25 | veinticinco | | + 21 | 24 | veinticuatro | | + 13 | 27 | veintisiete | | +(32 rows) + +SELECT a,b,c,substring(d for 30), length(d) from clstr_tst ORDER BY a; + a | b | c | substring | length +----+----+---------------+--------------------------------+-------- + 1 | 11 | once | | + 2 | 10 | diez | | + 3 | 31 | treinta y uno | | + 4 | 22 | veintidos | | + 5 | 3 | tres | | + 6 | 20 | veinte | | + 7 | 23 | veintitres | | + 8 | 21 | veintiuno | | + 9 | 4 | cuatro | | + 10 | 14 | catorce | | + 11 | 2 | dos | | + 12 | 18 | dieciocho | | + 13 | 27 | veintisiete | | + 14 | 25 | veinticinco | | + 15 | 13 | trece | | + 16 | 28 | veintiocho | | + 17 | 32 | treinta y dos | | + 18 | 5 | cinco | | + 19 | 29 | veintinueve | | + 20 | 1 | uno | | + 21 | 24 | veinticuatro | | + 22 | 30 | treinta | | + 23 | 12 | doce | | + 24 | 17 | diecisiete | | + 25 | 9 | nueve | | + 26 | 19 | diecinueve | | + 27 | 26 | veintiseis | | + 28 | 15 | quince | | + 29 | 7 | siete | | + 30 | 16 | dieciseis | | + 31 | 8 | ocho | | + 32 | 6 | seis | xyzzyxyzzyxyzzyxyzzyxyzzyxyzzy | 500000 +(32 rows) + +SELECT a,b,c,substring(d for 30), length(d) from clstr_tst ORDER BY b; + a | b | c | substring | length +----+----+---------------+--------------------------------+-------- + 20 | 1 | uno | | + 11 | 2 | dos | | + 5 | 3 | tres | | + 9 | 4 | cuatro | | + 18 | 5 | cinco | | + 32 | 6 | seis | xyzzyxyzzyxyzzyxyzzyxyzzyxyzzy | 500000 + 29 | 7 | siete | | + 31 | 8 | ocho | | + 25 | 9 | nueve | | + 2 | 10 | diez | | + 1 | 11 | once | | + 23 | 12 | doce | | + 15 | 13 | trece | | + 10 | 14 | catorce | | + 28 | 15 | quince | | + 30 | 16 | dieciseis | | + 24 | 17 | diecisiete | | + 12 | 18 | dieciocho | | + 26 | 19 | diecinueve | | + 6 | 20 | veinte | | + 8 | 21 | veintiuno | | + 4 | 22 | veintidos | | + 7 | 23 | veintitres | | + 21 | 24 | veinticuatro | | + 14 | 25 | veinticinco | | + 27 | 26 | veintiseis | | + 13 | 27 | veintisiete | | + 16 | 28 | veintiocho | | + 19 | 29 | veintinueve | | + 22 | 30 | treinta | | + 3 | 31 | treinta y uno | | + 17 | 32 | treinta y dos | | +(32 rows) + +SELECT a,b,c,substring(d for 30), length(d) from clstr_tst ORDER BY c; + a | b | c | substring | length +----+----+---------------+--------------------------------+-------- + 10 | 14 | catorce | | + 18 | 5 | cinco | | + 9 | 4 | cuatro | | + 26 | 19 | diecinueve | | + 12 | 18 | dieciocho | | + 30 | 16 | dieciseis | | + 24 | 17 | diecisiete | | + 2 | 10 | diez | | + 23 | 12 | doce | | + 11 | 2 | dos | | + 25 | 9 | nueve | | + 31 | 8 | ocho | | + 1 | 11 | once | | + 28 | 15 | quince | | + 32 | 6 | seis | xyzzyxyzzyxyzzyxyzzyxyzzyxyzzy | 500000 + 29 | 7 | siete | | + 15 | 13 | trece | | + 22 | 30 | treinta | | + 17 | 32 | treinta y dos | | + 3 | 31 | treinta y uno | | + 5 | 3 | tres | | + 20 | 1 | uno | | + 6 | 20 | veinte | | + 14 | 25 | veinticinco | | + 21 | 24 | veinticuatro | | + 4 | 22 | veintidos | | + 19 | 29 | veintinueve | | + 16 | 28 | veintiocho | | + 27 | 26 | veintiseis | | + 13 | 27 | veintisiete | | + 7 | 23 | veintitres | | + 8 | 21 | veintiuno | | +(32 rows) + +-- Verify that inheritance link still works +INSERT INTO clstr_tst_inh VALUES (0, 100, 'in child table'); +SELECT a,b,c,substring(d for 30), length(d) from clstr_tst; + a | b | c | substring | length +----+-----+----------------+--------------------------------+-------- + 18 | 5 | cinco | | + 24 | 17 | diecisiete | | + 2 | 10 | diez | | + 29 | 7 | siete | | + 22 | 30 | treinta | | + 3 | 31 | treinta y uno | | + 4 | 22 | veintidos | | + 19 | 29 | veintinueve | | + 16 | 28 | veintiocho | | + 27 | 26 | veintiseis | | + 7 | 23 | veintitres | | + 8 | 21 | veintiuno | | + 26 | 19 | diecinueve | | + 12 | 18 | dieciocho | | + 30 | 16 | dieciseis | | + 23 | 12 | doce | | + 31 | 8 | ocho | | + 1 | 11 | once | | + 15 | 13 | trece | | + 20 | 1 | uno | | + 0 | 100 | in child table | | + 10 | 14 | catorce | | + 9 | 4 | cuatro | | + 11 | 2 | dos | | + 25 | 9 | nueve | | + 28 | 15 | quince | | + 32 | 6 | seis | xyzzyxyzzyxyzzyxyzzyxyzzyxyzzy | 500000 + 17 | 32 | treinta y dos | | + 5 | 3 | tres | | + 6 | 20 | veinte | | + 14 | 25 | veinticinco | | + 21 | 24 | veinticuatro | | + 13 | 27 | veintisiete | | +(33 rows) + +-- Verify that foreign key link still works +INSERT INTO clstr_tst (b, c) VALUES (1111, 'this should fail'); +SELECT conname FROM pg_constraint WHERE conrelid = 'clstr_tst'::regclass +ORDER BY 1; + conname +---------------- + clstr_tst_con + clstr_tst_pkey +(2 rows) + +SELECT relname, relkind, + EXISTS(SELECT 1 FROM pg_class WHERE oid = c.reltoastrelid) AS hastoast +FROM pg_class c WHERE relname LIKE 'clstr_tst%' ORDER BY relname; + relname | relkind | hastoast +----------------------+---------+---------- + clstr_tst | r | f + clstr_tst_a_seq | S | f + clstr_tst_b | i | f + clstr_tst_b_c | i | f + clstr_tst_c | i | f + clstr_tst_c_b | i | f + clstr_tst_inh | r | f + clstr_tst_pkey | i | f + clstr_tst_s | r | f + clstr_tst_s_pkey | i | f + clstr_tst_s_rf_a_seq | S | f +(11 rows) + +-- Verify that indisclustered is correctly set +SELECT pg_class.relname FROM pg_index, pg_class, pg_class AS pg_class_2 +WHERE pg_class.oid=indexrelid + AND indrelid=pg_class_2.oid + AND pg_class_2.relname = 'clstr_tst' + AND indisclustered; + relname +------------- + clstr_tst_c +(1 row) + +-- Try changing indisclustered +ALTER TABLE clstr_tst CLUSTER ON clstr_tst_b_c; +SELECT pg_class.relname FROM pg_index, pg_class, pg_class AS pg_class_2 +WHERE pg_class.oid=indexrelid + AND indrelid=pg_class_2.oid + AND pg_class_2.relname = 'clstr_tst' + AND indisclustered; + relname +--------------- + clstr_tst_b_c +(1 row) + +-- Try turning off all clustering +ALTER TABLE clstr_tst SET WITHOUT CLUSTER; +SELECT pg_class.relname FROM pg_index, pg_class, pg_class AS pg_class_2 +WHERE pg_class.oid=indexrelid + AND indrelid=pg_class_2.oid + AND pg_class_2.relname = 'clstr_tst' + AND indisclustered; + relname +--------- +(0 rows) + +-- Verify that clustering all tables does in fact cluster the right ones +CREATE USER regress_clstr_user; +NOTICE: resource queue required -- using default resource queue "pg_default" +CREATE TABLE clstr_1 (a INT PRIMARY KEY); +CREATE TABLE clstr_2 (a INT PRIMARY KEY); +CREATE TABLE clstr_3 (a INT PRIMARY KEY); +ALTER TABLE clstr_1 OWNER TO regress_clstr_user; +ALTER TABLE clstr_3 OWNER TO regress_clstr_user; +GRANT SELECT ON clstr_2 TO regress_clstr_user; +INSERT INTO clstr_1 VALUES (2); +INSERT INTO clstr_1 VALUES (1); +INSERT INTO clstr_2 VALUES (2); +INSERT INTO clstr_2 VALUES (1); +INSERT INTO clstr_3 VALUES (2); +INSERT INTO clstr_3 VALUES (1); +-- "CLUSTER " on a table that hasn't been clustered +CLUSTER clstr_2; +ERROR: there is no previously clustered index or cluster_columns reloptions for table "clstr_2" +CLUSTER clstr_1_pkey ON clstr_1; +CLUSTER clstr_2 USING clstr_2_pkey; +SELECT * FROM clstr_1 UNION ALL + SELECT * FROM clstr_2 UNION ALL + SELECT * FROM clstr_3; + a +--- + 2 + 2 + 2 + 1 + 1 + 1 +(6 rows) + +-- revert to the original state +DELETE FROM clstr_1; +DELETE FROM clstr_2; +DELETE FROM clstr_3; +INSERT INTO clstr_1 VALUES (2); +INSERT INTO clstr_1 VALUES (1); +INSERT INTO clstr_2 VALUES (2); +INSERT INTO clstr_2 VALUES (1); +INSERT INTO clstr_3 VALUES (2); +INSERT INTO clstr_3 VALUES (1); +-- this user can only cluster clstr_1 and clstr_3, but the latter +-- has not been clustered +SET SESSION AUTHORIZATION regress_clstr_user; +CLUSTER; +SELECT * FROM clstr_1 UNION ALL + SELECT * FROM clstr_2 UNION ALL + SELECT * FROM clstr_3; + a +--- + 1 + 1 + 1 + 2 + 2 + 2 +(6 rows) + +-- cluster a single table using the indisclustered bit previously set +DELETE FROM clstr_1; +INSERT INTO clstr_1 VALUES (2); +INSERT INTO clstr_1 VALUES (1); +CLUSTER clstr_1; +SELECT * FROM clstr_1; + a +--- + 1 + 2 +(2 rows) + +-- Test MVCC-safety of cluster. There isn't much we can do to verify the +-- results with a single backend... +CREATE TABLE clustertest (key int, distkey int) DISTRIBUTED BY (distkey); +CREATE INDEX clustertest_pkey ON clustertest (key); +INSERT INTO clustertest VALUES (10, 1); +INSERT INTO clustertest VALUES (20, 2); +INSERT INTO clustertest VALUES (30, 1); +INSERT INTO clustertest VALUES (40, 2); +INSERT INTO clustertest VALUES (50, 3); +-- Use a transaction so that updates are not committed when CLUSTER sees 'em +BEGIN; +-- Test update where the old row version is found first in the scan +UPDATE clustertest SET key = 100 WHERE key = 10; +-- Test update where the new row version is found first in the scan +UPDATE clustertest SET key = 35 WHERE key = 40; +-- Test longer update chain +UPDATE clustertest SET key = 60 WHERE key = 50; +UPDATE clustertest SET key = 70 WHERE key = 60; +UPDATE clustertest SET key = 80 WHERE key = 70; +SELECT key FROM clustertest; + key +----- + 30 + 100 + 20 + 35 + 80 +(5 rows) + +CLUSTER clustertest_pkey ON clustertest; +SELECT key FROM clustertest; + key +----- + 20 + 35 + 80 + 30 + 100 +(5 rows) + +COMMIT; +SELECT key FROM clustertest; + key +----- + 20 + 35 + 80 + 30 + 100 +(5 rows) + +-- check that temp tables can be clustered +create temp table clstr_temp (col1 int primary key, col2 text); +insert into clstr_temp values (2, 'two'), (1, 'one'); +cluster clstr_temp using clstr_temp_pkey; +select * from clstr_temp; + col1 | col2 +------+------ + 2 | two + 1 | one +(2 rows) + +drop table clstr_temp; +RESET SESSION AUTHORIZATION; +-- check clustering an empty table +DROP TABLE clustertest; +CREATE TABLE clustertest (f1 int PRIMARY KEY); +CLUSTER clustertest USING clustertest_pkey; +CLUSTER clustertest; +-- Check that partitioned tables cannot be clustered +CREATE TABLE clstrpart (a int) PARTITION BY RANGE (a); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. +CREATE INDEX clstrpart_idx ON clstrpart (a); +ALTER TABLE clstrpart CLUSTER ON clstrpart_idx; +ERROR: cannot mark index clustered in partitioned table +CLUSTER clstrpart USING clstrpart_idx; +ERROR: cannot cluster a partitioned table +DROP TABLE clstrpart; +-- Test CLUSTER with external tuplesorting +-- The tests assume that the rows come out in the physical order, as +-- sorted by CLUSTER. In GPDB, add a dummy column to force all the rows to go +-- to the same segment, otherwise the rows come out in random order from the +-- segments. +create table clstr_4 as select 1 as dummy, * from tenk1 distributed by (dummy); +create index cluster_sort on clstr_4 (hundred, thousand, tenthous); +-- ensure we don't use the index in CLUSTER nor the checking SELECTs +set enable_indexscan = off; +-- Use external sort: +set maintenance_work_mem = '1MB'; +cluster clstr_4 using cluster_sort; +select * from +(select hundred, lag(hundred) over () as lhundred, + thousand, lag(thousand) over () as lthousand, + tenthous, lag(tenthous) over () as ltenthous from clstr_4) ss +where row(hundred, thousand, tenthous) <= row(lhundred, lthousand, ltenthous); + hundred | lhundred | thousand | lthousand | tenthous | ltenthous +---------+----------+----------+-----------+----------+----------- +(0 rows) + +reset enable_indexscan; +reset maintenance_work_mem; +-- test CLUSTER on expression index +CREATE TABLE clstr_expression(id serial primary key, a int, b text COLLATE "C"); +INSERT INTO clstr_expression(a, b) SELECT g.i % 42, 'prefix'||g.i FROM generate_series(1, 133) g(i); +CREATE INDEX clstr_expression_minus_a ON clstr_expression ((-a), b); +CREATE INDEX clstr_expression_upper_b ON clstr_expression ((upper(b))); +-- enable to keep same plan with pg +SET gp_enable_relsize_collection= on; +-- verify indexes work before cluster +BEGIN; +SET LOCAL enable_seqscan = false; +EXPLAIN (COSTS OFF) SELECT * FROM clstr_expression WHERE upper(b) = 'PREFIX3'; + QUERY PLAN +---------------------------------------------- + Gather Motion 3:1 (slice1; segments: 3) + -> Seq Scan on clstr_expression + Filter: (upper(b) = 'PREFIX3'::text) + Optimizer: GPORCA +(4 rows) + +SELECT * FROM clstr_expression WHERE upper(b) = 'PREFIX3'; + id | a | b +----+---+--------- + 3 | 3 | prefix3 +(1 row) + +EXPLAIN (COSTS OFF) SELECT * FROM clstr_expression WHERE -a = -3 ORDER BY -a, b; + QUERY PLAN +------------------------------------------------------------ + Result + -> Sort + Sort Key: ((- a)), b COLLATE "C" + -> Result + -> Gather Motion 3:1 (slice1; segments: 3) + -> Seq Scan on clstr_expression + Filter: ((- a) = '-3'::integer) + Optimizer: GPORCA +(8 rows) + +SELECT * FROM clstr_expression WHERE -a = -3 ORDER BY -a, b; + id | a | b +-----+---+----------- + 129 | 3 | prefix129 + 3 | 3 | prefix3 + 45 | 3 | prefix45 + 87 | 3 | prefix87 +(4 rows) + +COMMIT; +-- and after clustering on clstr_expression_minus_a +CLUSTER clstr_expression USING clstr_expression_minus_a; +WITH rows AS + (SELECT ctid, lag(a) OVER (PARTITION BY gp_segment_id ORDER BY ctid) AS la, a FROM clstr_expression) +SELECT * FROM rows WHERE la < a; + ctid | la | a +------+----+--- +(0 rows) + +BEGIN; +SET LOCAL enable_seqscan = false; +EXPLAIN (COSTS OFF) SELECT * FROM clstr_expression WHERE upper(b) = 'PREFIX3'; + QUERY PLAN +---------------------------------------------- + Gather Motion 3:1 (slice1; segments: 3) + -> Seq Scan on clstr_expression + Filter: (upper(b) = 'PREFIX3'::text) + Optimizer: GPORCA +(4 rows) + +SELECT * FROM clstr_expression WHERE upper(b) = 'PREFIX3'; + id | a | b +----+---+--------- + 3 | 3 | prefix3 +(1 row) + +EXPLAIN (COSTS OFF) SELECT * FROM clstr_expression WHERE -a = -3 ORDER BY -a, b; + QUERY PLAN +------------------------------------------------------------ + Result + -> Sort + Sort Key: ((- a)), b COLLATE "C" + -> Result + -> Gather Motion 3:1 (slice1; segments: 3) + -> Seq Scan on clstr_expression + Filter: ((- a) = '-3'::integer) + Optimizer: GPORCA +(8 rows) + +SELECT * FROM clstr_expression WHERE -a = -3 ORDER BY -a, b; + id | a | b +-----+---+----------- + 129 | 3 | prefix129 + 3 | 3 | prefix3 + 45 | 3 | prefix45 + 87 | 3 | prefix87 +(4 rows) + +COMMIT; +-- and after clustering on clstr_expression_upper_b +CLUSTER clstr_expression USING clstr_expression_upper_b; +WITH rows AS + (SELECT ctid, lag(b) OVER (PARTITION BY gp_segment_id ORDER BY ctid) AS lb, b FROM clstr_expression) +SELECT * FROM rows WHERE upper(lb) > upper(b); + ctid | lb | b +------+----+--- +(0 rows) + +BEGIN; +SET LOCAL enable_seqscan = false; +EXPLAIN (COSTS OFF) SELECT * FROM clstr_expression WHERE upper(b) = 'PREFIX3'; + QUERY PLAN +---------------------------------------------- + Gather Motion 3:1 (slice1; segments: 3) + -> Seq Scan on clstr_expression + Filter: (upper(b) = 'PREFIX3'::text) + Optimizer: GPORCA +(4 rows) + +SELECT * FROM clstr_expression WHERE upper(b) = 'PREFIX3'; + id | a | b +----+---+--------- + 3 | 3 | prefix3 +(1 row) + +EXPLAIN (COSTS OFF) SELECT * FROM clstr_expression WHERE -a = -3 ORDER BY -a, b; + QUERY PLAN +------------------------------------------------------------ + Result + -> Sort + Sort Key: ((- a)), b COLLATE "C" + -> Result + -> Gather Motion 3:1 (slice1; segments: 3) + -> Seq Scan on clstr_expression + Filter: ((- a) = '-3'::integer) + Optimizer: GPORCA +(8 rows) + +SELECT * FROM clstr_expression WHERE -a = -3 ORDER BY -a, b; + id | a | b +-----+---+----------- + 129 | 3 | prefix129 + 3 | 3 | prefix3 + 45 | 3 | prefix45 + 87 | 3 | prefix87 +(4 rows) + +COMMIT; +-- clean up +SET gp_enable_relsize_collection= off; +DROP TABLE clustertest; +DROP TABLE clstr_1; +DROP TABLE clstr_2; +DROP TABLE clstr_3; +DROP TABLE clstr_4; +DROP TABLE clstr_expression; +DROP USER regress_clstr_user; +-- Test transactional safety of CLUSTER against heap +CREATE TABLE foo (a int, b varchar, c int) DISTRIBUTED BY (a); +INSERT INTO foo SELECT i, 'initial insert' || i, i FROM generate_series(1,10000)i; +CREATE index ifoo on foo using btree (b); +-- execute cluster in a transaction but don't commit the transaction +BEGIN; +CLUSTER foo USING ifoo; +ABORT; +-- try cluster again +CLUSTER foo USING ifoo; +DROP TABLE foo; diff --git a/contrib/pax_storage/src/test/regress/expected/equivclass_optimizer.out b/contrib/pax_storage/src/test/regress/expected/equivclass_optimizer.out index 172e1ca6ca1..4b47c1c4a3d 100644 --- a/contrib/pax_storage/src/test/regress/expected/equivclass_optimizer.out +++ b/contrib/pax_storage/src/test/regress/expected/equivclass_optimizer.out @@ -509,13 +509,17 @@ create temp view overview as select f1::information_schema.sql_identifier as sqli, f2 from undername; explain (costs off) -- this should not require a sort select * from overview where sqli = 'foo' order by sqli; - QUERY PLAN ------------------------------------------- - Gather Motion 1:1 (slice1; segments: 1) - -> Seq Scan on undername - Filter: (f1 = 'foo'::name) - Optimizer: Postgres query optimizer -(4 rows) + QUERY PLAN +----------------------------------------------------------------------------------------- + Gather Motion 3:1 (slice1; segments: 3) + Merge Key: ((f1)::information_schema.sql_identifier) + -> Sort + Sort Key: ((f1)::information_schema.sql_identifier) + -> Result + Filter: ((((f1)::information_schema.sql_identifier))::name = 'foo'::name) + -> Seq Scan on undername + Optimizer: GPORCA +(8 rows) reset optimizer_enable_hashjoin; reset optimizer_enable_mergejoin; diff --git a/contrib/pax_storage/src/test/regress/expected/gp_array_agg_optimizer.out b/contrib/pax_storage/src/test/regress/expected/gp_array_agg_optimizer.out index 64cf06e8597..77876ee1389 100644 --- a/contrib/pax_storage/src/test/regress/expected/gp_array_agg_optimizer.out +++ b/contrib/pax_storage/src/test/regress/expected/gp_array_agg_optimizer.out @@ -333,18 +333,17 @@ from arrtest; $query$ AS qry \gset EXPLAIN (COSTS OFF, VERBOSE) :qry ; - QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - Finalize Aggregate + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------ + Aggregate Output: array_agg(a), array_dims(array_agg(b)), array_dims(array_agg(c)), array_agg(d), array_dims(array_agg(e)), array_agg(f), array_agg(g) -> Gather Motion 3:1 (slice1; segments: 3) - Output: (PARTIAL array_agg(a)), (PARTIAL array_agg(b)), (PARTIAL array_agg(c)), (PARTIAL array_agg(d)), (PARTIAL array_agg(e)), (PARTIAL array_agg(f)), (PARTIAL array_agg(g)) - -> Partial Aggregate - Output: PARTIAL array_agg(a), PARTIAL array_agg(b), PARTIAL array_agg(c), PARTIAL array_agg(d), PARTIAL array_agg(e), PARTIAL array_agg(f), PARTIAL array_agg(g) - -> Seq Scan on test_gp_array_agg.arrtest - Output: a, b, c, d, e, f, g - Optimizer: Postgres query optimizer -(9 rows) + Output: a, b, c, d, e, f, g + -> Seq Scan on test_gp_array_agg.arrtest + Output: a, b, c, d, e, f, g + Settings: enable_parallel = 'off', optimizer = 'on' + Optimizer: GPORCA +(8 rows) :qry ; agg_a | dims_b | dims_c | agg_d | dims_e | agg_f | agg_g diff --git a/contrib/pax_storage/src/test/regress/expected/rowsecurity_optimizer.out b/contrib/pax_storage/src/test/regress/expected/rowsecurity_optimizer.out index c5229896acf..5e8cae258d1 100644 --- a/contrib/pax_storage/src/test/regress/expected/rowsecurity_optimizer.out +++ b/contrib/pax_storage/src/test/regress/expected/rowsecurity_optimizer.out @@ -455,18 +455,19 @@ EXPLAIN (COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle); (4 rows) EXPLAIN (COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle); - QUERY PLAN ---------------------------------------------------------------------------- + QUERY PLAN +---------------------------------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) -> Hash Join - Hash Cond: (category.cid = document.cid) - -> Broadcast Motion 3:3 (slice2; segments: 3) - -> Seq Scan on category - -> Hash + Hash Cond: (document.cid = category.cid) + -> Redistribute Motion 3:3 (slice2; segments: 3) + Hash Key: document.cid -> Seq Scan on document Filter: ((dauthor = 'regress_rls_carol'::name) AND f_leak(dtitle)) - Optimizer: Postgres query optimizer -(9 rows) + -> Hash + -> Seq Scan on category + Optimizer: GPORCA +(10 rows) -- interaction of FK/PK constraints SET SESSION AUTHORIZATION regress_rls_alice; @@ -1294,18 +1295,14 @@ NOTICE: f_leak => great technology book (3 rows) EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle); - QUERY PLAN ---------------------------------------------------------------------- + QUERY PLAN +---------------------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) - -> Append - -> Seq Scan on part_document_fiction part_document_1 - Filter: ((dauthor = 'regress_rls_carol'::name) AND f_leak(dtitle)) - -> Seq Scan on part_document_satire part_document_2 - Filter: ((dauthor = 'regress_rls_carol'::name) AND f_leak(dtitle)) - -> Seq Scan on part_document_nonfiction part_document_3 - Filter: ((dauthor = 'regress_rls_carol'::name) AND f_leak(dtitle)) - Optimizer: Postgres query optimizer -(9 rows) + -> Dynamic Seq Scan on part_document + Number of partitions to scan: 3 (out of 3) + Filter: ((dauthor = 'regress_rls_carol'::name) AND f_leak(dtitle)) + Optimizer: GPORCA +(5 rows) -- database superuser does bypass RLS policy when enabled RESET SESSION AUTHORIZATION; diff --git a/contrib/pax_storage/src/test/regress/expected/select_views_optimizer.out b/contrib/pax_storage/src/test/regress/expected/select_views_optimizer.out index e5691b5969a..974faf13ca2 100644 --- a/contrib/pax_storage/src/test/regress/expected/select_views_optimizer.out +++ b/contrib/pax_storage/src/test/regress/expected/select_views_optimizer.out @@ -1331,9 +1331,7 @@ SET SESSION AUTHORIZATION regress_alice; -- cost of the function, do we want to do that? -- end_ignore SELECT * FROM my_property_normal WHERE f_leak(passwd); -NOTICE: f_leak => passwd123 (seg1 slice1 127.0.0.1:7003 pid=9756) -NOTICE: f_leak => beafsteak (seg1 slice1 127.0.0.1:7003 pid=9756) -NOTICE: f_leak => hamburger (seg1 slice1 127.0.0.1:7003 pid=9756) +NOTICE: f_leak => passwd123 (seg1 slice1 172.18.0.2:7003 pid=1007708) cid | name | tel | passwd -----+---------------+------------------+----------- 101 | regress_alice | +81-12-3456-7890 | passwd123 @@ -1344,12 +1342,12 @@ NOTICE: f_leak => hamburger (seg1 slice1 127.0.0.1:7003 pid=9756) -- cost of the function, do we want to do that? -- end_ignore EXPLAIN (COSTS OFF) SELECT * FROM my_property_normal WHERE f_leak(passwd); - QUERY PLAN ------------------------------------------------------------- + QUERY PLAN +--------------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) -> Seq Scan on customer - Filter: (f_leak(passwd) AND (name = 'regress_alice'::name)) - Optimizer: Postgres query optimizer + Filter: ((name = 'regress_alice'::name) AND f_leak(passwd)) + Optimizer: GPORCA (4 rows) SELECT * FROM my_property_secure WHERE f_leak(passwd); @@ -1376,12 +1374,8 @@ EXPLAIN (COSTS OFF) SELECT * FROM my_property_secure WHERE f_leak(passwd); -- SELECT * FROM my_property_normal v WHERE f_leak('passwd') AND f_leak(passwd); -NOTICE: f_leak => passwd (seg1 slice1 127.0.0.1:7003 pid=16817) -NOTICE: f_leak => passwd123 (seg1 slice1 127.0.0.1:7003 pid=16817) -NOTICE: f_leak => passwd (seg1 slice1 127.0.0.1:7003 pid=16817) -NOTICE: f_leak => beafsteak (seg1 slice1 127.0.0.1:7003 pid=16817) -NOTICE: f_leak => passwd (seg1 slice1 127.0.0.1:7003 pid=16817) -NOTICE: f_leak => hamburger (seg1 slice1 127.0.0.1:7003 pid=16817) +NOTICE: f_leak => passwd (seg1 slice1 172.18.0.2:7003 pid=1007708) +NOTICE: f_leak => passwd123 (seg1 slice1 172.18.0.2:7003 pid=1007708) cid | name | tel | passwd -----+---------------+------------------+----------- 101 | regress_alice | +81-12-3456-7890 | passwd123 @@ -1389,12 +1383,12 @@ NOTICE: f_leak => hamburger (seg1 slice1 127.0.0.1:7003 pid=16817) EXPLAIN (COSTS OFF) SELECT * FROM my_property_normal v WHERE f_leak('passwd') AND f_leak(passwd); - QUERY PLAN ---------------------------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------------------------ Gather Motion 3:1 (slice1; segments: 3) -> Seq Scan on customer - Filter: (f_leak('passwd'::text) AND f_leak(passwd) AND (name = 'regress_alice'::name)) - Optimizer: Postgres query optimizer + Filter: ((name = 'regress_alice'::name) AND f_leak('passwd'::text) AND f_leak(passwd)) + Optimizer: GPORCA (4 rows) SELECT * FROM my_property_secure v @@ -1439,13 +1433,13 @@ EXPLAIN (COSTS OFF) SELECT * FROM my_credit_card_normal WHERE f_leak(cnum); -------------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) -> Hash Join - Hash Cond: ((r.cid = l.cid) AND (r.dist_key = l.dist_key)) - -> Seq Scan on credit_card r - Filter: f_leak(cnum) + Hash Cond: ((l.cid = r.cid) AND (l.dist_key = r.dist_key)) + -> Seq Scan on customer l + Filter: (name = 'regress_alice'::name) -> Hash - -> Seq Scan on customer l - Filter: (name = 'regress_alice'::name) - Optimizer: Postgres query optimizer + -> Seq Scan on credit_card r + Filter: f_leak(cnum) + Optimizer: GPORCA (9 rows) SELECT * FROM my_credit_card_secure WHERE f_leak(cnum); @@ -1551,9 +1545,7 @@ EXPLAIN (COSTS OFF) SELECT * FROM my_credit_card_usage_secure PREPARE p1 AS SELECT * FROM my_property_normal WHERE f_leak(passwd); PREPARE p2 AS SELECT * FROM my_property_secure WHERE f_leak(passwd); EXECUTE p1; -NOTICE: f_leak => passwd123 (seg1 slice1 127.0.0.1:7003 pid=16817) -NOTICE: f_leak => beafsteak (seg1 slice1 127.0.0.1:7003 pid=16817) -NOTICE: f_leak => hamburger (seg1 slice1 127.0.0.1:7003 pid=16817) +NOTICE: f_leak => passwd123 (seg1 slice1 172.18.0.2:7003 pid=1007708) cid | name | tel | passwd -----+---------------+------------------+----------- 101 | regress_alice | +81-12-3456-7890 | passwd123 @@ -1578,9 +1570,7 @@ NOTICE: f_leak => passwd123 (seg1 slice1 127.0.0.1:7003 pid=16817) (1 row) EXECUTE p2; -- To be perform as a view without security-barrier -NOTICE: f_leak => passwd123 (seg1 slice1 127.0.0.1:7003 pid=16817) -NOTICE: f_leak => beafsteak (seg1 slice1 127.0.0.1:7003 pid=16817) -NOTICE: f_leak => hamburger (seg1 slice1 127.0.0.1:7003 pid=16817) +NOTICE: f_leak => passwd123 (seg1 slice1 172.18.0.2:7003 pid=1007708) cid | name | tel | passwd -----+---------------+------------------+----------- 101 | regress_alice | +81-12-3456-7890 | passwd123 diff --git a/contrib/pax_storage/src/test/regress/expected/stats_ext_optimizer.out b/contrib/pax_storage/src/test/regress/expected/stats_ext_optimizer.out index a3818d9bf0c..3753a25ebf8 100644 --- a/contrib/pax_storage/src/test/regress/expected/stats_ext_optimizer.out +++ b/contrib/pax_storage/src/test/regress/expected/stats_ext_optimizer.out @@ -3037,7 +3037,7 @@ ANALYZE expr_stats; SELECT * FROM check_estimated_rows('SELECT * FROM expr_stats WHERE a = 0 AND (b || c) <= ''z'' AND (c || b) >= ''0'''); estimated | actual -----------+-------- - 11 | 100 + 68 | 100 (1 row) CREATE STATISTICS expr_stats_1 (mcv) ON a, b, (b || c), (c || b) FROM expr_stats; @@ -3045,7 +3045,7 @@ ANALYZE expr_stats; SELECT * FROM check_estimated_rows('SELECT * FROM expr_stats WHERE a = 0 AND (b || c) <= ''z'' AND (c || b) >= ''0'''); estimated | actual -----------+-------- - 100 | 100 + 68 | 100 (1 row) DROP TABLE expr_stats; diff --git a/contrib/pax_storage/src/test/regress/expected/subselect_optimizer.out b/contrib/pax_storage/src/test/regress/expected/subselect_optimizer.out index 12035c92509..665f4d728be 100644 --- a/contrib/pax_storage/src/test/regress/expected/subselect_optimizer.out +++ b/contrib/pax_storage/src/test/regress/expected/subselect_optimizer.out @@ -790,17 +790,33 @@ select * from outer_text where (f1, f2) not in (select * from inner_text); -- explain (verbose, costs off) select 'foo'::text in (select 'bar'::name union all select 'bar'::name); - QUERY PLAN -------------------------------------- - Result - Output: (hashed SubPlan 1) - SubPlan 1 - -> Append - -> Result - Output: 'bar'::name - -> Result - Output: 'bar'::name -(8 rows) + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Nested Loop Left Join + Output: ((CASE WHEN (count(*) = '0'::bigint) THEN '0'::bigint WHEN (count(*) = sum((CASE WHEN (('bar'::name) IS NULL) THEN 1 ELSE 0 END))) THEN '-1'::bigint ELSE count(*) END) > '0'::bigint) + Join Filter: true + -> Result + Output: true + -> Materialize + Output: (CASE WHEN (count(*) = '0'::bigint) THEN '0'::bigint WHEN (count(*) = sum((CASE WHEN (('bar'::name) IS NULL) THEN 1 ELSE 0 END))) THEN '-1'::bigint ELSE count(*) END) + -> Aggregate + Output: CASE WHEN (count(*) = '0'::bigint) THEN '0'::bigint WHEN (count(*) = sum((CASE WHEN (('bar'::name) IS NULL) THEN 1 ELSE 0 END))) THEN '-1'::bigint ELSE count(*) END + -> Result + Output: CASE WHEN (('bar'::name) IS NULL) THEN 1 ELSE 0 END + -> Append + -> Result + Output: ('bar'::name) + Filter: (('foo'::text = ('bar'::name)) OR (('bar'::name) IS NULL)) + -> Result + Output: 'bar'::name + -> Result + Output: ('bar'::name) + Filter: (('foo'::text = ('bar'::name)) OR (('bar'::name) IS NULL)) + -> Result + Output: 'bar'::name + Settings: enable_parallel = 'off', optimizer = 'on' + Optimizer: GPORCA +(24 rows) select 'foo'::text in (select 'bar'::name union all select 'bar'::name); ?column? @@ -1007,19 +1023,20 @@ reset optimizer; explain (verbose, costs off) select x, x from (select (select current_database()) as x from (values(1),(2)) v(y)) ss; - QUERY PLAN --------------------------------------- - Values Scan on "*VALUES*" - Output: $0, $1 - InitPlan 1 (returns $0) - -> Result - Output: 'regression'::name - InitPlan 2 (returns $1) - -> Result - Output: 'regression'::name - Optimizer: Postgres query optimizer - Settings: optimizer=on -(9 rows) + QUERY PLAN +------------------------------------------------------ + Nested Loop Left Join + Output: ('regression'::name), ('regression'::name) + Join Filter: true + -> Values Scan on "Values" + Output: column1 + -> Materialize + Output: ('regression'::name) + -> Result + Output: 'regression'::name + Settings: enable_parallel = 'off', optimizer = 'on' + Optimizer: GPORCA +(11 rows) explain (verbose, costs off) select x, x from @@ -1039,21 +1056,20 @@ explain (verbose, costs off) explain (verbose, costs off) select x, x from (select (select current_database() where y=y) as x from (values(1),(2)) v(y)) ss; - QUERY PLAN ----------------------------------------------------------------------- - Values Scan on "*VALUES*" - Output: (SubPlan 1), (SubPlan 2) - SubPlan 1 - -> Result - Output: 'regression'::name - One-Time Filter: ("*VALUES*".column1 = "*VALUES*".column1) - SubPlan 2 - -> Result - Output: 'regression'::name - One-Time Filter: ("*VALUES*".column1 = "*VALUES*".column1) - Optimizer: Postgres query optimizer - Settings: optimizer=on -(9 rows) + QUERY PLAN +------------------------------------------------------ + Nested Loop Left Join + Output: ('regression'::name), ('regression'::name) + Join Filter: ("Values".column1 = "Values".column1) + -> Values Scan on "Values" + Output: column1 + -> Materialize + Output: ('regression'::name) + -> Result + Output: 'regression'::name + Settings: enable_parallel = 'off', optimizer = 'on' + Optimizer: GPORCA +(11 rows) explain (verbose, costs off) select x, x from @@ -1069,7 +1085,7 @@ explain (verbose, costs off) Output: random() One-Time Filter: ("*VALUES*".column1 = "*VALUES*".column1) Optimizer: Postgres query optimizer -(9 rows) +(10 rows) -- -- Test rescan of a hashed subplan (the use of random() is to prevent the @@ -1701,15 +1717,15 @@ select * from x where f1 = 1; explain (verbose, costs off) with x as (select * from (select f1, current_database() from subselect_tbl) ss) select * from x where f1 = 1; - QUERY PLAN ------------------------------------------------------- + QUERY PLAN +---------------------------------------------------------------------------- Gather Motion 1:1 (slice1; segments: 1) - Output: subselect_tbl.f1, ('regression'::name) + Output: f1, ('regression'::name) -> Seq Scan on public.subselect_tbl - Output: subselect_tbl.f1, 'regression'::name + Output: f1, 'regression'::name Filter: (subselect_tbl.f1 = 1) - Optimizer: Postgres query optimizer - Settings: optimizer=off + Settings: enable_parallel = 'off', gp_cte_sharing = 'on', optimizer = 'on' + Optimizer: GPORCA (7 rows) -- Volatile functions prevent inlining @@ -1763,56 +1779,84 @@ select * from x where f1 = 1; explain (verbose, costs off) with x as (select * from (select f1, current_database() as n from subselect_tbl) ss) select * from x, x x2 where x.n = x2.n; - QUERY PLAN ------------------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------------ Gather Motion 3:1 (slice1; segments: 3) - Output: share0_ref2.f1, share0_ref2.n, share0_ref1.f1, share0_ref1.n - -> Hash Join - Output: share0_ref2.f1, share0_ref2.n, share0_ref1.f1, share0_ref1.n - Hash Cond: (share0_ref2.n = share0_ref1.n) - -> Redistribute Motion 3:3 (slice2; segments: 3) - Output: share0_ref2.f1, share0_ref2.n - Hash Key: share0_ref2.n - -> Shared Scan (share slice:id 2:0) - Output: share0_ref2.f1, share0_ref2.n - -> Hash + Output: share0_ref3.f1, share0_ref3.n, share0_ref2.f1, share0_ref2.n + -> Sequence + Output: share0_ref3.f1, share0_ref3.n, share0_ref2.f1, share0_ref2.n + -> Shared Scan (share slice:id 1:0) Output: share0_ref1.f1, share0_ref1.n - -> Redistribute Motion 3:3 (slice3; segments: 3) - Output: share0_ref1.f1, share0_ref1.n - Hash Key: share0_ref1.n - -> Shared Scan (share slice:id 3:0) - Output: share0_ref1.f1, share0_ref1.n - -> Seq Scan on public.subselect_tbl - Output: subselect_tbl.f1, 'regression'::name - Settings: gp_cte_sharing=on, optimizer=on - Optimizer: Postgres query optimizer -(21 rows) + -> Result + Output: subselect_tbl.f1, ('regression'::name) + Filter: (('regression'::name) = 'regression'::name) + -> Seq Scan on public.subselect_tbl + Output: 'regression'::name, subselect_tbl.f1 + -> Hash Join + Output: share0_ref3.f1, share0_ref3.n, share0_ref2.f1, share0_ref2.n + Hash Cond: (share0_ref3.n = share0_ref2.n) + -> Redistribute Motion 3:3 (slice2; segments: 3) + Output: share0_ref3.f1, share0_ref3.n + Hash Key: share0_ref3.n + -> Result + Output: share0_ref3.f1, share0_ref3.n + Filter: (share0_ref3.n = 'regression'::name) + -> Shared Scan (share slice:id 2:0) + Output: share0_ref3.f1, share0_ref3.n + -> Hash + Output: share0_ref2.f1, share0_ref2.n + -> Redistribute Motion 3:3 (slice3; segments: 3) + Output: share0_ref2.f1, share0_ref2.n + Hash Key: share0_ref2.n + -> Result + Output: share0_ref2.f1, share0_ref2.n + Filter: (share0_ref2.n = 'regression'::name) + -> Shared Scan (share slice:id 3:0) + Output: share0_ref2.f1, share0_ref2.n + Settings: enable_parallel = 'off', gp_cte_sharing = 'on', optimizer = 'on' + Optimizer: GPORCA +(34 rows) explain (verbose, costs off) with x as not materialized (select * from (select f1, current_database() as n from subselect_tbl) ss) select * from x, x x2 where x.n = x2.n; - QUERY PLAN --------------------------------------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------------ Gather Motion 3:1 (slice1; segments: 3) - Output: subselect_tbl.f1, ('regression'::name), subselect_tbl_1.f1, ('regression'::name) - -> Hash Join - Output: subselect_tbl.f1, ('regression'::name), subselect_tbl_1.f1, ('regression'::name) - Hash Cond: (('regression'::name) = ('regression'::name)) - -> Redistribute Motion 3:3 (slice2; segments: 3) - Output: subselect_tbl.f1, ('regression'::name) - Hash Key: ('regression'::name) - -> Seq Scan on public.subselect_tbl - Output: subselect_tbl.f1, 'regression'::name - -> Hash - Output: subselect_tbl_1.f1, ('regression'::name) - -> Redistribute Motion 3:3 (slice3; segments: 3) - Output: subselect_tbl_1.f1, ('regression'::name) - Hash Key: ('regression'::name) - -> Seq Scan on public.subselect_tbl subselect_tbl_1 - Output: subselect_tbl_1.f1, 'regression'::name - Optimizer: Postgres query optimizer - Settings: gp_cte_sharing=on, optimizer=on -(19 rows) + Output: share0_ref3.f1, share0_ref3.n, share0_ref2.f1, share0_ref2.n + -> Sequence + Output: share0_ref3.f1, share0_ref3.n, share0_ref2.f1, share0_ref2.n + -> Shared Scan (share slice:id 1:0) + Output: share0_ref1.f1, share0_ref1.n + -> Result + Output: subselect_tbl.f1, ('regression'::name) + Filter: (('regression'::name) = 'regression'::name) + -> Seq Scan on public.subselect_tbl + Output: 'regression'::name, subselect_tbl.f1 + -> Hash Join + Output: share0_ref3.f1, share0_ref3.n, share0_ref2.f1, share0_ref2.n + Hash Cond: (share0_ref3.n = share0_ref2.n) + -> Redistribute Motion 3:3 (slice2; segments: 3) + Output: share0_ref3.f1, share0_ref3.n + Hash Key: share0_ref3.n + -> Result + Output: share0_ref3.f1, share0_ref3.n + Filter: (share0_ref3.n = 'regression'::name) + -> Shared Scan (share slice:id 2:0) + Output: share0_ref3.f1, share0_ref3.n + -> Hash + Output: share0_ref2.f1, share0_ref2.n + -> Redistribute Motion 3:3 (slice3; segments: 3) + Output: share0_ref2.f1, share0_ref2.n + Hash Key: share0_ref2.n + -> Result + Output: share0_ref2.f1, share0_ref2.n + Filter: (share0_ref2.n = 'regression'::name) + -> Shared Scan (share slice:id 3:0) + Output: share0_ref2.f1, share0_ref2.n + Settings: enable_parallel = 'off', gp_cte_sharing = 'on', optimizer = 'on' + Optimizer: GPORCA +(34 rows) -- Multiply-referenced CTEs can't be inlined if they contain outer self-refs -- start_ignore @@ -1828,8 +1872,8 @@ with recursive x(a) as select z.a || z1.a as a from z cross join z as z1 where length(z.a || z1.a) < 5)) select * from x; - QUERY PLAN ---------------------------------------------------- + QUERY PLAN +---------------------------------------------------------------------------- Recursive Union -> Values Scan on "*VALUES*" Output: "*VALUES*".column1 @@ -1838,13 +1882,11 @@ select * from x; Join Filter: (length((x.a || x_1.a)) < 5) -> WorkTable Scan on x Output: x.a - -> Materialize + -> WorkTable Scan on x x_1 Output: x_1.a - -> WorkTable Scan on x x_1 - Output: x_1.a + Settings: enable_parallel = 'off', gp_cte_sharing = 'on', optimizer = 'on' Optimizer: Postgres query optimizer - Settings: gp_cte_sharing=on, optimizer=on -(14 rows) +(12 rows) with recursive x(a) as ((values ('a'), ('b')) diff --git a/contrib/pax_storage/src/test/regress/expected/union_optimizer.out b/contrib/pax_storage/src/test/regress/expected/union_optimizer.out index 7351e93fd83..bc9005feaa3 100644 --- a/contrib/pax_storage/src/test/regress/expected/union_optimizer.out +++ b/contrib/pax_storage/src/test/regress/expected/union_optimizer.out @@ -1297,12 +1297,19 @@ explain (costs off) UNION ALL SELECT 2 AS t, * FROM tenk1 b) c WHERE t = 2; - QUERY PLAN ------------------------------------------- + QUERY PLAN +------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) - -> Seq Scan on tenk1 b - Optimizer: Postgres query optimizer -(3 rows) + -> Append + -> Result + One-Time Filter: (gp_execution_segment() = 0) + -> Result + One-Time Filter: false + -> Result + Filter: ((2) = 2) + -> Seq Scan on tenk1 b + Optimizer: GPORCA +(10 rows) -- Test that we push quals into UNION sub-selects only when it's safe explain (costs off) diff --git a/contrib/pax_storage/src/test/regress/init_file b/contrib/pax_storage/src/test/regress/init_file index 63ef563d715..7f2d8338ab9 100644 --- a/contrib/pax_storage/src/test/regress/init_file +++ b/contrib/pax_storage/src/test/regress/init_file @@ -19,6 +19,8 @@ m/^ Optimizer: GPORCA/ m/^ Optimizer: Postgres-based planner/ m/^ Settings:.*/ +m/^DETAIL: Falling back to Postgres-based planner because GPORCA does not support the following feature:.*/ + # There are a number of NOTICE and HINT messages around table distribution, # for example to inform the user that the database will pick a particular # column in order to distribute the data. Merging tests from postgres will From 12a33573cb09d15c475e48d94f4eb927611a9359 Mon Sep 17 00:00:00 2001 From: Jianghua Yang Date: Tue, 31 Mar 2026 06:41:46 +0800 Subject: [PATCH 4/6] ORCA: fall back to planner for ORDER BY with ordering operators (amcanorderbyop) ORCA does not support amcanorderbyop (KNN ordered index scans). Queries like `ORDER BY col <-> 'value' LIMIT N` on GiST indexes cannot produce ordered index scans in ORCA, resulting in inefficient Seq Scan + Sort plans instead of KNN-GiST Index Scan. Previously, these queries would accidentally get correct plans because column-level COLLATE "C" caused a blanket fallback to the PostgreSQL planner, which does support amcanorderbyop. After commit 3f4ce85ae6c added COLLATE "C" support to ORCA, these queries lost their fallback path. Add has_orderby_ordering_op() in walkers.c to detect when a query's ORDER BY clause contains an operator registered as AMOP_ORDER in pg_amop (e.g., <-> for trigram/point distance). When detected, ORCA falls back to the PostgreSQL planner which can generate KNN ordered index scans. The check is precise: only ORDER BY with ordering operators triggers fallback. Other queries on the same tables (WHERE with LIKE/%%, equality filters, etc.) continue to use ORCA normally. --- .../pg_trgm/expected/pg_trgm_optimizer.out | 166 +++++++++--------- src/backend/gpopt/gpdbwrappers.cpp | 11 ++ .../gpopt/translate/CTranslatorQueryToDXL.cpp | 9 + src/backend/optimizer/util/walkers.c | 53 ++++++ src/include/gpopt/gpdbwrappers.h | 3 + src/include/optimizer/walkers.h | 1 + 6 files changed, 156 insertions(+), 87 deletions(-) diff --git a/contrib/pg_trgm/expected/pg_trgm_optimizer.out b/contrib/pg_trgm/expected/pg_trgm_optimizer.out index 4597b8ca047..a2b012fa8fb 100644 --- a/contrib/pg_trgm/expected/pg_trgm_optimizer.out +++ b/contrib/pg_trgm/expected/pg_trgm_optimizer.out @@ -4665,17 +4665,16 @@ select count(*) from test_trgm where t ~ '[qwerty]{2}-?[qwerty]{2}'; -- check handling of indexquals that generate no searchable conditions explain (costs off) select count(*) from test_trgm where t like '%99%' and t like '%qwerty%'; - QUERY PLAN ------------------------------------------------------------------------------------------ - Finalize Aggregate + QUERY PLAN +----------------------------------------------------------------------------------- + Aggregate -> Gather Motion 3:1 (slice1; segments: 3) - -> Partial Aggregate - -> Bitmap Heap Scan on test_trgm - Recheck Cond: ((t ~~ '%99%'::text) AND (t ~~ '%qwerty%'::text)) - -> Bitmap Index Scan on trgm_idx - Index Cond: ((t ~~ '%99%'::text) AND (t ~~ '%qwerty%'::text)) - Optimizer: Postgres query optimizer -(8 rows) + -> Bitmap Heap Scan on test_trgm + Recheck Cond: ((t ~~ '%99%'::text) AND (t ~~ '%qwerty%'::text)) + -> Bitmap Index Scan on trgm_idx + Index Cond: ((t ~~ '%99%'::text) AND (t ~~ '%qwerty%'::text)) + Optimizer: GPORCA +(7 rows) select count(*) from test_trgm where t like '%99%' and t like '%qwerty%'; count @@ -4685,17 +4684,16 @@ select count(*) from test_trgm where t like '%99%' and t like '%qwerty%'; explain (costs off) select count(*) from test_trgm where t like '%99%' and t like '%qw%'; - QUERY PLAN -------------------------------------------------------------------------------------- - Finalize Aggregate + QUERY PLAN +------------------------------------------------------------------------------- + Aggregate -> Gather Motion 3:1 (slice1; segments: 3) - -> Partial Aggregate - -> Bitmap Heap Scan on test_trgm - Recheck Cond: ((t ~~ '%99%'::text) AND (t ~~ '%qw%'::text)) - -> Bitmap Index Scan on trgm_idx - Index Cond: ((t ~~ '%99%'::text) AND (t ~~ '%qw%'::text)) - Optimizer: Postgres query optimizer -(8 rows) + -> Bitmap Heap Scan on test_trgm + Recheck Cond: ((t ~~ '%99%'::text) AND (t ~~ '%qw%'::text)) + -> Bitmap Index Scan on trgm_idx + Index Cond: ((t ~~ '%99%'::text) AND (t ~~ '%qw%'::text)) + Optimizer: GPORCA +(7 rows) select count(*) from test_trgm where t like '%99%' and t like '%qw%'; count @@ -4709,17 +4707,16 @@ create index t_trgm_idx on t_test_trgm using gin (t gin_trgm_ops); insert into t_test_trgm values ('qwerty99'), ('qwerty01'); explain (costs off) select count(*) from t_test_trgm where t like '%99%' and t like '%qwerty%'; - QUERY PLAN ------------------------------------------------------------------------------------------ - Finalize Aggregate + QUERY PLAN +----------------------------------------------------------------------------------- + Aggregate -> Gather Motion 3:1 (slice1; segments: 3) - -> Partial Aggregate - -> Bitmap Heap Scan on t_test_trgm - Recheck Cond: ((t ~~ '%99%'::text) AND (t ~~ '%qwerty%'::text)) - -> Bitmap Index Scan on t_trgm_idx - Index Cond: ((t ~~ '%99%'::text) AND (t ~~ '%qwerty%'::text)) - Optimizer: Postgres query optimizer -(8 rows) + -> Bitmap Heap Scan on t_test_trgm + Recheck Cond: ((t ~~ '%99%'::text) AND (t ~~ '%qwerty%'::text)) + -> Bitmap Index Scan on t_trgm_idx + Index Cond: ((t ~~ '%99%'::text) AND (t ~~ '%qwerty%'::text)) + Optimizer: GPORCA +(7 rows) select count(*) from t_test_trgm where t like '%99%' and t like '%qwerty%'; count @@ -4729,17 +4726,16 @@ select count(*) from t_test_trgm where t like '%99%' and t like '%qwerty%'; explain (costs off) select count(*) from t_test_trgm where t like '%99%' and t like '%qw%'; - QUERY PLAN -------------------------------------------------------------------------------------- - Finalize Aggregate + QUERY PLAN +------------------------------------------------------------------------------- + Aggregate -> Gather Motion 3:1 (slice1; segments: 3) - -> Partial Aggregate - -> Bitmap Heap Scan on t_test_trgm - Recheck Cond: ((t ~~ '%99%'::text) AND (t ~~ '%qw%'::text)) - -> Bitmap Index Scan on t_trgm_idx - Index Cond: ((t ~~ '%99%'::text) AND (t ~~ '%qw%'::text)) - Optimizer: Postgres query optimizer -(8 rows) + -> Bitmap Heap Scan on t_test_trgm + Recheck Cond: ((t ~~ '%99%'::text) AND (t ~~ '%qw%'::text)) + -> Bitmap Index Scan on t_trgm_idx + Index Cond: ((t ~~ '%99%'::text) AND (t ~~ '%qw%'::text)) + Optimizer: GPORCA +(7 rows) select count(*) from t_test_trgm where t like '%99%' and t like '%qw%'; count @@ -5094,27 +5090,25 @@ create index test2_idx_gist on test2 using gist (t gist_trgm_ops); set enable_seqscan=off; explain (costs off) select * from test2 where t like '%BCD%'; - QUERY PLAN -------------------------------------------------- + QUERY PLAN +------------------------------------------------ Gather Motion 3:1 (slice1; segments: 3) - -> Bitmap Heap Scan on test2 - Recheck Cond: (t ~~ '%BCD%'::text) - -> Bitmap Index Scan on test2_idx_gist - Index Cond: (t ~~ '%BCD%'::text) - Optimizer: Postgres query optimizer -(6 rows) + -> Index Scan using test2_idx_gist on test2 + Index Cond: (t ~~ '%BCD%'::text) + Filter: (t ~~ '%BCD%'::text) + Optimizer: GPORCA +(5 rows) explain (costs off) select * from test2 where t ilike '%BCD%'; - QUERY PLAN -------------------------------------------------- + QUERY PLAN +------------------------------------------------ Gather Motion 3:1 (slice1; segments: 3) - -> Bitmap Heap Scan on test2 - Recheck Cond: (t ~~* '%BCD%'::text) - -> Bitmap Index Scan on test2_idx_gist - Index Cond: (t ~~* '%BCD%'::text) - Optimizer: Postgres query optimizer -(6 rows) + -> Index Scan using test2_idx_gist on test2 + Index Cond: (t ~~* '%BCD%'::text) + Filter: (t ~~* '%BCD%'::text) + Optimizer: GPORCA +(5 rows) select * from test2 where t like '%BCD%'; t @@ -5159,27 +5153,25 @@ select * from test2 where t like ' z foo%'; explain (costs off) select * from test2 where t ~ '[abc]{3}'; - QUERY PLAN --------------------------------------------------- + QUERY PLAN +------------------------------------------------ Gather Motion 3:1 (slice1; segments: 3) - -> Bitmap Heap Scan on test2 - Recheck Cond: (t ~ '[abc]{3}'::text) - -> Bitmap Index Scan on test2_idx_gist - Index Cond: (t ~ '[abc]{3}'::text) - Optimizer: Postgres query optimizer -(6 rows) + -> Index Scan using test2_idx_gist on test2 + Index Cond: (t ~ '[abc]{3}'::text) + Filter: (t ~ '[abc]{3}'::text) + Optimizer: GPORCA +(5 rows) explain (costs off) select * from test2 where t ~* 'DEF'; - QUERY PLAN -------------------------------------------------- + QUERY PLAN +------------------------------------------------ Gather Motion 3:1 (slice1; segments: 3) - -> Bitmap Heap Scan on test2 - Recheck Cond: (t ~* 'DEF'::text) - -> Bitmap Index Scan on test2_idx_gist - Index Cond: (t ~* 'DEF'::text) - Optimizer: Postgres query optimizer -(6 rows) + -> Index Scan using test2_idx_gist on test2 + Index Cond: (t ~* 'DEF'::text) + Filter: (t ~* 'DEF'::text) + Optimizer: GPORCA +(5 rows) select * from test2 where t ~ '[abc]{3}'; t @@ -5311,14 +5303,14 @@ select * from test2 where t ~ '/\d+/-\d'; -- test = operator explain (costs off) select * from test2 where t = 'abcdef'; - QUERY PLAN ------------------------------------------- + QUERY PLAN +------------------------------------------------ Gather Motion 1:1 (slice1; segments: 1) - -> Bitmap Heap Scan on test2 - Recheck Cond: (t = 'abcdef'::text) - -> Bitmap Index Scan on test2_idx_gist - Index Cond: (t = 'abcdef'::text) -(2 rows) + -> Index Scan using test2_idx_gist on test2 + Index Cond: (t = 'abcdef'::text) + Filter: (t = 'abcdef'::text) + Optimizer: GPORCA +(5 rows) select * from test2 where t = 'abcdef'; t @@ -5328,14 +5320,14 @@ select * from test2 where t = 'abcdef'; explain (costs off) select * from test2 where t = '%line%'; - QUERY PLAN ------------------------------------------- + QUERY PLAN +------------------------------------------------ Gather Motion 1:1 (slice1; segments: 1) - -> Bitmap Heap Scan on test2 - Recheck Cond: (t = '%line%'::text) - -> Bitmap Index Scan on test2_idx_gist - Index Cond: (t = '%line%'::text) -(6 rows) + -> Index Scan using test2_idx_gist on test2 + Index Cond: (t = '%line%'::text) + Filter: (t = '%line%'::text) + Optimizer: GPORCA +(5 rows) select * from test2 where t = '%line%'; t @@ -5423,7 +5415,7 @@ SELECT DISTINCT city, similarity(city, 'Warsaw'), show_limit() -> Index Scan using restaurants_city_idx on restaurants Index Cond: (city % 'Warsaw'::text) Filter: (city % 'Warsaw'::text) - Optimizer: Pivotal Optimizer (GPORCA) + Optimizer: GPORCA (9 rows) SELECT set_limit(0.3); diff --git a/src/backend/gpopt/gpdbwrappers.cpp b/src/backend/gpopt/gpdbwrappers.cpp index 005db9e0eb3..e9406ffd2e7 100644 --- a/src/backend/gpopt/gpdbwrappers.cpp +++ b/src/backend/gpopt/gpdbwrappers.cpp @@ -2030,6 +2030,17 @@ gpdb::CheckCollation(Node *node) return -1; } +bool +gpdb::HasOrderByOrderingOp(Query *query) +{ + GP_WRAP_START; + { + return has_orderby_ordering_op(query); + } + GP_WRAP_END; + return false; +} + Node * gpdb::CoerceToCommonType(ParseState *pstate, Node *node, Oid target_type, const char *context) diff --git a/src/backend/gpopt/translate/CTranslatorQueryToDXL.cpp b/src/backend/gpopt/translate/CTranslatorQueryToDXL.cpp index 20cc6557c28..99d87917b38 100644 --- a/src/backend/gpopt/translate/CTranslatorQueryToDXL.cpp +++ b/src/backend/gpopt/translate/CTranslatorQueryToDXL.cpp @@ -324,6 +324,15 @@ CTranslatorQueryToDXL::CheckUnsupportedNodeTypes(Query *query) GPOS_RAISE(gpdxl::ExmaDXL, gpdxl::ExmiQuery2DXLUnsupportedFeature, GPOS_WSZ_LIT("Non-default collation")); } + + // ORCA does not support amcanorderbyop (KNN ordered index scans). + // Fall back to the PostgreSQL planner for queries whose ORDER BY + // contains an ordering operator (e.g., <-> for distance). + if (gpdb::HasOrderByOrderingOp(query)) + { + GPOS_RAISE(gpdxl::ExmaDXL, gpdxl::ExmiQuery2DXLUnsupportedFeature, + GPOS_WSZ_LIT("ORDER BY with ordering operator (amcanorderbyop)")); + } } //--------------------------------------------------------------------------- diff --git a/src/backend/optimizer/util/walkers.c b/src/backend/optimizer/util/walkers.c index 13f06bf5ddd..0c8093a089a 100644 --- a/src/backend/optimizer/util/walkers.c +++ b/src/backend/optimizer/util/walkers.c @@ -8,12 +8,17 @@ #include "postgres.h" +#include "access/htup_details.h" +#include "catalog/pg_amop.h" #include "catalog/pg_collation.h" #include "catalog/pg_type.h" #include "miscadmin.h" #include "nodes/nodeFuncs.h" +#include "optimizer/optimizer.h" #include "optimizer/walkers.h" +#include "utils/catcache.h" #include "utils/lsyscache.h" +#include "utils/syscache.h" /** * Plan node walker related methods. @@ -1129,3 +1134,51 @@ check_collation_walker(Node *node, check_collation_context *context) } } +/* + * has_orderby_ordering_op + * + * Check if any ORDER BY expression in the query uses an ordering operator + * (amoppurpose = AMOP_ORDER in pg_amop). These operators (e.g., <-> for + * trigram distance or point distance) require amcanorderbyop index support + * (KNN-GiST) which ORCA does not implement. Return true if such an + * operator is found, signaling that the query should fall back to the + * PostgreSQL planner. + */ +bool +has_orderby_ordering_op(Query *query) +{ + ListCell *lc; + + if (query->sortClause == NIL) + return false; + + foreach(lc, query->sortClause) + { + SortGroupClause *sgc = (SortGroupClause *) lfirst(lc); + TargetEntry *tle = get_sortgroupclause_tle(sgc, query->targetList); + Node *expr = (Node *) tle->expr; + + if (!IsA(expr, OpExpr)) + continue; + + Oid opno = ((OpExpr *) expr)->opno; + CatCList *catlist = SearchSysCacheList1(AMOPOPID, + ObjectIdGetDatum(opno)); + + for (int i = 0; i < catlist->n_members; i++) + { + HeapTuple tp = &catlist->members[i]->tuple; + Form_pg_amop amop = (Form_pg_amop) GETSTRUCT(tp); + + if (amop->amoppurpose == AMOP_ORDER) + { + ReleaseSysCacheList(catlist); + return true; + } + } + ReleaseSysCacheList(catlist); + } + + return false; +} + diff --git a/src/include/gpopt/gpdbwrappers.h b/src/include/gpopt/gpdbwrappers.h index 4470d053248..d6700e90e50 100644 --- a/src/include/gpopt/gpdbwrappers.h +++ b/src/include/gpopt/gpdbwrappers.h @@ -673,6 +673,9 @@ int FindNodes(Node *node, List *nodeTags); // look for nodes with non-default collation; returns 1 if any exist, -1 otherwise int CheckCollation(Node *node); +// check if ORDER BY uses an ordering operator (amcanorderbyop) unsupported by ORCA +bool HasOrderByOrderingOp(Query *query); + Node *CoerceToCommonType(ParseState *pstate, Node *node, Oid target_type, const char *context); diff --git a/src/include/optimizer/walkers.h b/src/include/optimizer/walkers.h index 6d0d38717f5..d29bc5551e8 100644 --- a/src/include/optimizer/walkers.h +++ b/src/include/optimizer/walkers.h @@ -43,5 +43,6 @@ extern List *extract_nodes_plan(Plan *pl, int nodeTag, bool descendIntoSubquerie extern List *extract_nodes_expression(Node *node, int nodeTag, bool descendIntoSubqueries); extern int find_nodes(Node *node, List *nodeTags); extern int check_collation(Node *node); +extern bool has_orderby_ordering_op(Query *query); #endif /* WALKERS_H_ */ From 61eff1ff8fb3f946619517af7065131913f6c938 Mon Sep 17 00:00:00 2001 From: Jianghua Yang Date: Tue, 31 Mar 2026 07:00:24 +0800 Subject: [PATCH 5/6] ORCA: refine amcanorderbyop fallback to skip lossy distance functions Only fall back to the PostgreSQL planner when ALL ordering-operator expressions in ORDER BY have at least one direct Var (column reference) argument. Expressions like "circle(p,1) <-> point(0,0)" wrap the column in a function call, which can cause "lossy distance functions are not supported in index-only scans" errors in the planner. Leave such queries for ORCA to handle via Seq Scan + Sort. --- src/backend/optimizer/util/walkers.c | 93 ++++++++++++++----- .../expected/create_index_optimizer.out | 45 +++++---- src/test/regress/expected/gist_optimizer.out | 63 ++++++------- 3 files changed, 121 insertions(+), 80 deletions(-) diff --git a/src/backend/optimizer/util/walkers.c b/src/backend/optimizer/util/walkers.c index 0c8093a089a..bbe8927c4cd 100644 --- a/src/backend/optimizer/util/walkers.c +++ b/src/backend/optimizer/util/walkers.c @@ -1134,20 +1134,71 @@ check_collation_walker(Node *node, check_collation_context *context) } } +/* + * is_ordering_op + * + * Return true if the operator is registered as an ordering operator + * (amoppurpose = AMOP_ORDER) in any opfamily in pg_amop. + */ +static bool +is_ordering_op(Oid opno) +{ + CatCList *catlist = SearchSysCacheList1(AMOPOPID, + ObjectIdGetDatum(opno)); + + for (int i = 0; i < catlist->n_members; i++) + { + HeapTuple tp = &catlist->members[i]->tuple; + Form_pg_amop amop = (Form_pg_amop) GETSTRUCT(tp); + + if (amop->amoppurpose == AMOP_ORDER) + { + ReleaseSysCacheList(catlist); + return true; + } + } + ReleaseSysCacheList(catlist); + return false; +} + +/* + * has_plain_var_arg + * + * Return true if the OpExpr has at least one direct Var argument + * (not wrapped in a function or other expression). + */ +static bool +has_plain_var_arg(OpExpr *op) +{ + ListCell *arg_lc; + + foreach(arg_lc, op->args) + { + if (IsA(lfirst(arg_lc), Var)) + return true; + } + return false; +} + /* * has_orderby_ordering_op * - * Check if any ORDER BY expression in the query uses an ordering operator - * (amoppurpose = AMOP_ORDER in pg_amop). These operators (e.g., <-> for - * trigram distance or point distance) require amcanorderbyop index support - * (KNN-GiST) which ORCA does not implement. Return true if such an - * operator is found, signaling that the query should fall back to the - * PostgreSQL planner. + * Check if the query's ORDER BY uses ordering operators (amoppurpose = + * AMOP_ORDER in pg_amop) that the PostgreSQL planner can safely optimize + * with KNN-GiST index scans but ORCA cannot. + * + * Return true only when ALL ordering-operator expressions in ORDER BY + * have at least one direct Var (column reference) argument. Expressions + * like "circle(p,1) <-> point(0,0)" wrap the column in a function, + * which can cause "lossy distance functions are not supported in + * index-only scans" errors in the planner. In such cases we leave the + * query for ORCA to handle via Seq Scan + Sort. */ bool has_orderby_ordering_op(Query *query) { ListCell *lc; + bool found_ordering_op = false; if (query->sortClause == NIL) return false; @@ -1161,24 +1212,24 @@ has_orderby_ordering_op(Query *query) if (!IsA(expr, OpExpr)) continue; - Oid opno = ((OpExpr *) expr)->opno; - CatCList *catlist = SearchSysCacheList1(AMOPOPID, - ObjectIdGetDatum(opno)); + OpExpr *opexpr = (OpExpr *) expr; - for (int i = 0; i < catlist->n_members; i++) - { - HeapTuple tp = &catlist->members[i]->tuple; - Form_pg_amop amop = (Form_pg_amop) GETSTRUCT(tp); + if (!is_ordering_op(opexpr->opno)) + continue; - if (amop->amoppurpose == AMOP_ORDER) - { - ReleaseSysCacheList(catlist); - return true; - } - } - ReleaseSysCacheList(catlist); + /* + * Found an ordering operator. Check that at least one argument is + * a plain Var. If any ordering operator has only computed arguments + * (e.g., function calls wrapping columns), bail out immediately — + * falling back to the planner could produce lossy distance errors + * in index-only scans. + */ + found_ordering_op = true; + + if (!has_plain_var_arg(opexpr)) + return false; } - return false; + return found_ordering_op; } diff --git a/src/test/regress/expected/create_index_optimizer.out b/src/test/regress/expected/create_index_optimizer.out index dd3751bb6fc..192dfff8e7e 100644 --- a/src/test/regress/expected/create_index_optimizer.out +++ b/src/test/regress/expected/create_index_optimizer.out @@ -652,18 +652,16 @@ SELECT * FROM point_tblv WHERE f1 IS NOT NULL ORDER BY f1 <-> '0,1'; --SELECT * FROM point_tbl WHERE f1 IS NOT NULL ORDER BY f1 <-> '0,1'; EXPLAIN (COSTS OFF) SELECT * FROM point_tblv WHERE f1 <@ '(-10,-10),(10,10)':: box ORDER BY f1 <-> '0,1'; - QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - Result - -> Sort - Sort Key: ((f1 <-> '(0,1)'::point)) - -> Result - -> Gather Motion 3:1 (slice1; segments: 3) - -> Index Scan using gpointind on point_tbl - Index Cond: (f1 <@ '(10,10),(-10,-10)'::box) - Filter: ((f1 <> '(1e-300,-1e-300)'::point) AND ((f1 <-> '(0,0)'::point) <> 'Infinity'::double precision) AND (f1 <@ '(10,10),(-10,-10)'::box)) - Optimizer: Pivotal Optimizer (GPORCA) -(9 rows) + QUERY PLAN +------------------------------------------------------------------------------------------------------------------- + Gather Motion 3:1 (slice1; segments: 3) + Merge Key: ((point_tbl.f1 <-> '(0,1)'::point)) + -> Index Only Scan using gpointind on point_tbl + Index Cond: (f1 <@ '(10,10),(-10,-10)'::box) + Order By: (f1 <-> '(0,1)'::point) + Filter: ((f1 <> '(1e-300,-1e-300)'::point) AND ((f1 <-> '(0,0)'::point) <> 'Infinity'::double precision)) + Optimizer: Postgres query optimizer +(7 rows) SELECT * FROM point_tblv WHERE f1 <@ '(-10,-10),(10,10)':: box ORDER BY f1 <-> '0,1'; f1 @@ -767,18 +765,19 @@ SET enable_indexscan = OFF; SET enable_bitmapscan = ON; EXPLAIN (COSTS OFF) SELECT * FROM point_tblv WHERE f1 <@ '(-10,-10),(10,10)':: box ORDER BY f1 <-> '0,1'; - QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - Result + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------- + Gather Motion 3:1 (slice1; segments: 3) + Merge Key: ((point_tbl.f1 <-> '(0,1)'::point)) -> Sort - Sort Key: ((f1 <-> '(0,1)'::point)) - -> Result - -> Gather Motion 3:1 (slice1; segments: 3) - -> Index Scan using gpointind on point_tbl - Index Cond: (f1 <@ '(10,10),(-10,-10)'::box) - Filter: ((f1 <> '(1e-300,-1e-300)'::point) AND ((f1 <-> '(0,0)'::point) <> 'Infinity'::double precision) AND (f1 <@ '(10,10),(-10,-10)'::box)) - Optimizer: Pivotal Optimizer (GPORCA) -(9 rows) + Sort Key: ((point_tbl.f1 <-> '(0,1)'::point)) + -> Bitmap Heap Scan on point_tbl + Recheck Cond: (f1 <@ '(10,10),(-10,-10)'::box) + Filter: ((f1 <> '(1e-300,-1e-300)'::point) AND ((f1 <-> '(0,0)'::point) <> 'Infinity'::double precision)) + -> Bitmap Index Scan on gpointind + Index Cond: (f1 <@ '(10,10),(-10,-10)'::box) + Optimizer: Postgres query optimizer +(10 rows) SELECT * FROM point_tblv WHERE f1 <@ '(-10,-10),(10,10)':: box ORDER BY f1 <-> '0,1'; f1 diff --git a/src/test/regress/expected/gist_optimizer.out b/src/test/regress/expected/gist_optimizer.out index e9020c5db70..abb8b5524cf 100644 --- a/src/test/regress/expected/gist_optimizer.out +++ b/src/test/regress/expected/gist_optimizer.out @@ -98,18 +98,15 @@ select p from gist_tbl where p <@ box(point(0,0), point(0.5, 0.5)); explain (costs off) select p from gist_tbl where p <@ box(point(0,0), point(0.5, 0.5)) order by p <-> point(0.201, 0.201); - QUERY PLAN ---------------------------------------------------------------------- - Result - -> Gather Motion 3:1 (slice1; segments: 3) - Merge Key: ((p <-> '(0.201,0.201)'::point)) - -> Sort - Sort Key: ((p <-> '(0.201,0.201)'::point)) - -> Index Scan using gist_tbl_point_index on gist_tbl - Index Cond: (p <@ '(0.5,0.5),(0,0)'::box) - Filter: (p <@ '(0.5,0.5),(0,0)'::box) - Optimizer: Pivotal Optimizer (GPORCA) version 3.83.0 -(9 rows) + QUERY PLAN +-------------------------------------------------------------- + Gather Motion 3:1 (slice1; segments: 3) + Merge Key: ((p <-> '(0.201,0.201)'::point)) + -> Index Only Scan using gist_tbl_point_index on gist_tbl + Index Cond: (p <@ '(0.5,0.5),(0,0)'::box) + Order By: (p <-> '(0.201,0.201)'::point) + Optimizer: Postgres query optimizer +(6 rows) select p from gist_tbl where p <@ box(point(0,0), point(0.5, 0.5)) order by p <-> point(0.201, 0.201); @@ -132,18 +129,15 @@ order by p <-> point(0.201, 0.201); explain (costs off) select p from gist_tbl where p <@ box(point(0,0), point(0.5, 0.5)) order by point(0.101, 0.101) <-> p; - QUERY PLAN ---------------------------------------------------------------------- - Result - -> Gather Motion 3:1 (slice1; segments: 3) - Merge Key: (('(0.101,0.101)'::point <-> p)) - -> Sort - Sort Key: (('(0.101,0.101)'::point <-> p)) - -> Index Scan using gist_tbl_point_index on gist_tbl - Index Cond: (p <@ '(0.5,0.5),(0,0)'::box) - Filter: (p <@ '(0.5,0.5),(0,0)'::box) - Optimizer: Pivotal Optimizer (GPORCA) version 3.83.0 -(9 rows) + QUERY PLAN +-------------------------------------------------------------- + Gather Motion 3:1 (slice1; segments: 3) + Merge Key: (('(0.101,0.101)'::point <-> p)) + -> Index Only Scan using gist_tbl_point_index on gist_tbl + Index Cond: (p <@ '(0.5,0.5),(0,0)'::box) + Order By: (p <-> '(0.101,0.101)'::point) + Optimizer: Postgres query optimizer +(6 rows) select p from gist_tbl where p <@ box(point(0,0), point(0.5, 0.5)) order by point(0.101, 0.101) <-> p; @@ -248,18 +242,15 @@ select b from gist_tbl where b <@ box(point(5,5), point(6,6)); explain (costs off) select b from gist_tbl where b <@ box(point(5,5), point(6,6)) order by b <-> point(5.2, 5.91); - QUERY PLAN -------------------------------------------------------------------- - Result - -> Gather Motion 3:1 (slice1; segments: 3) - Merge Key: ((b <-> '(5.2,5.91)'::point)) - -> Sort - Sort Key: ((b <-> '(5.2,5.91)'::point)) - -> Index Scan using gist_tbl_box_index on gist_tbl - Index Cond: (b <@ '(6,6),(5,5)'::box) - Filter: (b <@ '(6,6),(5,5)'::box) - Optimizer: Pivotal Optimizer (GPORCA) -(9 rows) + QUERY PLAN +------------------------------------------------------------ + Gather Motion 3:1 (slice1; segments: 3) + Merge Key: ((b <-> '(5.2,5.91)'::point)) + -> Index Only Scan using gist_tbl_box_index on gist_tbl + Index Cond: (b <@ '(6,6),(5,5)'::box) + Order By: (b <-> '(5.2,5.91)'::point) + Optimizer: Postgres query optimizer +(6 rows) select b from gist_tbl where b <@ box(point(5,5), point(6,6)) order by b <-> point(5.2, 5.91); From 8f7b81e5ac91fb4687f6e4cada3cb6e90005084d Mon Sep 17 00:00:00 2001 From: Jianghua Yang Date: Tue, 31 Mar 2026 09:32:02 +0800 Subject: [PATCH 6/6] fix btree_gist test --- .../btree_gist/expected/cash_optimizer.out | 11 ++++----- .../btree_gist/expected/date_optimizer.out | 7 +++--- .../btree_gist/expected/float4_optimizer.out | 7 +++--- .../btree_gist/expected/float8_optimizer.out | 7 +++--- .../btree_gist/expected/int2_optimizer.out | 7 +++--- .../btree_gist/expected/int4_optimizer.out | 7 +++--- .../btree_gist/expected/int8_optimizer.out | 7 +++--- .../expected/interval_optimizer.out | 24 +++++++++---------- .../btree_gist/expected/time_optimizer.out | 7 +++--- .../expected/timestamp_optimizer.out | 7 +++--- .../expected/timestamptz_optimizer.out | 7 +++--- 11 files changed, 44 insertions(+), 54 deletions(-) diff --git a/contrib/btree_gist/expected/cash_optimizer.out b/contrib/btree_gist/expected/cash_optimizer.out index 171dec7e511..f2c9ac07420 100644 --- a/contrib/btree_gist/expected/cash_optimizer.out +++ b/contrib/btree_gist/expected/cash_optimizer.out @@ -77,12 +77,11 @@ SELECT a, a <-> '21472.79' FROM moneytmp ORDER BY a <-> '21472.79' LIMIT 3; QUERY PLAN ------------------------------------------------------------ Limit - -> Sort - Sort Key: ((a <-> '$21,472.79'::money)) - -> Result - -> Gather Motion 3:1 (slice1; segments: 3) - -> Seq Scan on moneytmp - Optimizer: GPORCA + -> Gather Motion 3:1 (slice1; segments: 3) + Merge Key: ((a <-> '$21,472.79'::money)) + -> Limit + -> Index Only Scan using moneyidx on moneytmp + Order By: (a <-> '$21,472.79'::money) (7 rows) SELECT a, a <-> '21472.79' FROM moneytmp ORDER BY a <-> '21472.79' LIMIT 3; diff --git a/contrib/btree_gist/expected/date_optimizer.out b/contrib/btree_gist/expected/date_optimizer.out index a77041f847f..12269cf169b 100644 --- a/contrib/btree_gist/expected/date_optimizer.out +++ b/contrib/btree_gist/expected/date_optimizer.out @@ -79,10 +79,9 @@ SELECT a, a <-> '2001-02-13' FROM datetmp ORDER BY a <-> '2001-02-13' LIMIT 3; Limit -> Gather Motion 3:1 (slice1; segments: 3) Merge Key: ((a <-> '02-13-2001'::date)) - -> Sort - Sort Key: ((a <-> '02-13-2001'::date)) - -> Seq Scan on datetmp - Optimizer: GPORCA + -> Limit + -> Index Only Scan using dateidx on datetmp + Order By: (a <-> '02-13-2001'::date) (7 rows) SELECT a, a <-> '2001-02-13' FROM datetmp ORDER BY a <-> '2001-02-13' LIMIT 3; diff --git a/contrib/btree_gist/expected/float4_optimizer.out b/contrib/btree_gist/expected/float4_optimizer.out index cc40e9bd1ae..7b71a2f5112 100644 --- a/contrib/btree_gist/expected/float4_optimizer.out +++ b/contrib/btree_gist/expected/float4_optimizer.out @@ -79,10 +79,9 @@ SELECT a, a <-> '-179.0' FROM float4tmp ORDER BY a <-> '-179.0' LIMIT 3; Limit -> Gather Motion 3:1 (slice1; segments: 3) Merge Key: ((a <-> '-179'::real)) - -> Sort - Sort Key: ((a <-> '-179'::real)) - -> Seq Scan on float4tmp - Optimizer: GPORCA + -> Limit + -> Index Only Scan using float4idx on float4tmp + Order By: (a <-> '-179'::real) (7 rows) SELECT a, a <-> '-179.0' FROM float4tmp ORDER BY a <-> '-179.0' LIMIT 3; diff --git a/contrib/btree_gist/expected/float8_optimizer.out b/contrib/btree_gist/expected/float8_optimizer.out index 1bd96c44d3b..18e5c195286 100644 --- a/contrib/btree_gist/expected/float8_optimizer.out +++ b/contrib/btree_gist/expected/float8_optimizer.out @@ -79,10 +79,9 @@ SELECT a, a <-> '-1890.0' FROM float8tmp ORDER BY a <-> '-1890.0' LIMIT 3; Limit -> Gather Motion 3:1 (slice1; segments: 3) Merge Key: ((a <-> '-1890'::double precision)) - -> Sort - Sort Key: ((a <-> '-1890'::double precision)) - -> Seq Scan on float8tmp - Optimizer: GPORCA + -> Limit + -> Index Only Scan using float8idx on float8tmp + Order By: (a <-> '-1890'::double precision) (7 rows) SELECT a, a <-> '-1890.0' FROM float8tmp ORDER BY a <-> '-1890.0' LIMIT 3; diff --git a/contrib/btree_gist/expected/int2_optimizer.out b/contrib/btree_gist/expected/int2_optimizer.out index fdfc859097b..f8f6a428b93 100644 --- a/contrib/btree_gist/expected/int2_optimizer.out +++ b/contrib/btree_gist/expected/int2_optimizer.out @@ -79,10 +79,9 @@ SELECT a, a <-> '237' FROM int2tmp ORDER BY a <-> '237' LIMIT 3; Limit -> Gather Motion 3:1 (slice1; segments: 3) Merge Key: ((a <-> '237'::smallint)) - -> Sort - Sort Key: ((a <-> '237'::smallint)) - -> Seq Scan on int2tmp - Optimizer: GPORCA + -> Limit + -> Index Only Scan using int2idx on int2tmp + Order By: (a <-> '237'::smallint) (7 rows) SELECT a, a <-> '237' FROM int2tmp ORDER BY a <-> '237' LIMIT 3; diff --git a/contrib/btree_gist/expected/int4_optimizer.out b/contrib/btree_gist/expected/int4_optimizer.out index 67107e63bfa..6877fb09af5 100644 --- a/contrib/btree_gist/expected/int4_optimizer.out +++ b/contrib/btree_gist/expected/int4_optimizer.out @@ -79,10 +79,9 @@ SELECT a, a <-> '237' FROM int4tmp ORDER BY a <-> '237' LIMIT 3; Limit -> Gather Motion 3:1 (slice1; segments: 3) Merge Key: ((a <-> 237)) - -> Sort - Sort Key: ((a <-> 237)) - -> Seq Scan on int4tmp - Optimizer: GPORCA + -> Limit + -> Index Only Scan using int4idx on int4tmp + Order By: (a <-> 237) (7 rows) SELECT a, a <-> '237' FROM int4tmp ORDER BY a <-> '237' LIMIT 3; diff --git a/contrib/btree_gist/expected/int8_optimizer.out b/contrib/btree_gist/expected/int8_optimizer.out index ba8e21135e8..962dd314661 100644 --- a/contrib/btree_gist/expected/int8_optimizer.out +++ b/contrib/btree_gist/expected/int8_optimizer.out @@ -79,10 +79,9 @@ SELECT a, a <-> '464571291354841' FROM int8tmp ORDER BY a <-> '464571291354841' Limit -> Gather Motion 3:1 (slice1; segments: 3) Merge Key: ((a <-> '464571291354841'::bigint)) - -> Sort - Sort Key: ((a <-> '464571291354841'::bigint)) - -> Seq Scan on int8tmp - Optimizer: GPORCA + -> Limit + -> Index Only Scan using int8idx on int8tmp + Order By: (a <-> '464571291354841'::bigint) (7 rows) SELECT a, a <-> '464571291354841' FROM int8tmp ORDER BY a <-> '464571291354841' LIMIT 3; diff --git a/contrib/btree_gist/expected/interval_optimizer.out b/contrib/btree_gist/expected/interval_optimizer.out index f5afd17456b..f0a4e850aeb 100644 --- a/contrib/btree_gist/expected/interval_optimizer.out +++ b/contrib/btree_gist/expected/interval_optimizer.out @@ -74,15 +74,15 @@ SELECT count(*) FROM intervaltmp WHERE a > '199 days 21:21:23'::interval; EXPLAIN (COSTS OFF) SELECT a, a <-> '199 days 21:21:23' FROM intervaltmp ORDER BY a <-> '199 days 21:21:23' LIMIT 3; - QUERY PLAN ------------------------------------------------------------------------------------ + QUERY PLAN +--------------------------------------------------------------------------------------- Limit -> Gather Motion 3:1 (slice1; segments: 3) Merge Key: ((a <-> '@ 199 days 21 hours 21 mins 23 secs'::interval)) - -> Sort - Sort Key: ((a <-> '@ 199 days 21 hours 21 mins 23 secs'::interval)) - -> Seq Scan on intervaltmp - Optimizer: GPORCA + -> Limit + -> Index Only Scan using intervalidx on intervaltmp + Order By: (a <-> '@ 199 days 21 hours 21 mins 23 secs'::interval) + Optimizer: Postgres query optimizer (7 rows) SELECT a, a <-> '199 days 21:21:23' FROM intervaltmp ORDER BY a <-> '199 days 21:21:23' LIMIT 3; @@ -96,15 +96,15 @@ SELECT a, a <-> '199 days 21:21:23' FROM intervaltmp ORDER BY a <-> '199 days 21 SET enable_indexonlyscan=off; EXPLAIN (COSTS OFF) SELECT a, a <-> '199 days 21:21:23' FROM intervaltmp ORDER BY a <-> '199 days 21:21:23' LIMIT 3; - QUERY PLAN ------------------------------------------------------------------------------------ + QUERY PLAN +--------------------------------------------------------------------------------------- Limit -> Gather Motion 3:1 (slice1; segments: 3) Merge Key: ((a <-> '@ 199 days 21 hours 21 mins 23 secs'::interval)) - -> Sort - Sort Key: ((a <-> '@ 199 days 21 hours 21 mins 23 secs'::interval)) - -> Seq Scan on intervaltmp - Optimizer: GPORCA + -> Limit + -> Index Scan using intervalidx on intervaltmp + Order By: (a <-> '@ 199 days 21 hours 21 mins 23 secs'::interval) + Optimizer: Postgres query optimizer (7 rows) SELECT a, a <-> '199 days 21:21:23' FROM intervaltmp ORDER BY a <-> '199 days 21:21:23' LIMIT 3; diff --git a/contrib/btree_gist/expected/time_optimizer.out b/contrib/btree_gist/expected/time_optimizer.out index 590ada880b9..40d49e79b02 100644 --- a/contrib/btree_gist/expected/time_optimizer.out +++ b/contrib/btree_gist/expected/time_optimizer.out @@ -79,10 +79,9 @@ SELECT a, a <-> '10:57:11' FROM timetmp ORDER BY a <-> '10:57:11' LIMIT 3; Limit -> Gather Motion 3:1 (slice1; segments: 3) Merge Key: ((a <-> '10:57:11'::time without time zone)) - -> Sort - Sort Key: ((a <-> '10:57:11'::time without time zone)) - -> Seq Scan on timetmp - Optimizer: GPORCA + -> Limit + -> Index Only Scan using timeidx on timetmp + Order By: (a <-> '10:57:11'::time without time zone) (7 rows) SELECT a, a <-> '10:57:11' FROM timetmp ORDER BY a <-> '10:57:11' LIMIT 3; diff --git a/contrib/btree_gist/expected/timestamp_optimizer.out b/contrib/btree_gist/expected/timestamp_optimizer.out index 1b8e709fe90..85c3a1a5e5d 100644 --- a/contrib/btree_gist/expected/timestamp_optimizer.out +++ b/contrib/btree_gist/expected/timestamp_optimizer.out @@ -79,10 +79,9 @@ SELECT a, a <-> '2004-10-26 08:55:08' FROM timestamptmp ORDER BY a <-> '2004-10- Limit -> Gather Motion 3:1 (slice1; segments: 3) Merge Key: ((a <-> 'Tue Oct 26 08:55:08 2004'::timestamp without time zone)) - -> Sort - Sort Key: ((a <-> 'Tue Oct 26 08:55:08 2004'::timestamp without time zone)) - -> Seq Scan on timestamptmp - Optimizer: GPORCA + -> Limit + -> Index Only Scan using timestampidx on timestamptmp + Order By: (a <-> 'Tue Oct 26 08:55:08 2004'::timestamp without time zone) (7 rows) SELECT a, a <-> '2004-10-26 08:55:08' FROM timestamptmp ORDER BY a <-> '2004-10-26 08:55:08' LIMIT 3; diff --git a/contrib/btree_gist/expected/timestamptz_optimizer.out b/contrib/btree_gist/expected/timestamptz_optimizer.out index 2173c5dca35..a9e043f98a6 100644 --- a/contrib/btree_gist/expected/timestamptz_optimizer.out +++ b/contrib/btree_gist/expected/timestamptz_optimizer.out @@ -199,10 +199,9 @@ SELECT a, a <-> '2018-12-18 10:59:54 GMT+2' FROM timestamptztmp ORDER BY a <-> ' Limit -> Gather Motion 3:1 (slice1; segments: 3) Merge Key: ((a <-> 'Tue Dec 18 04:59:54 2018 PST'::timestamp with time zone)) - -> Sort - Sort Key: ((a <-> 'Tue Dec 18 04:59:54 2018 PST'::timestamp with time zone)) - -> Seq Scan on timestamptztmp - Optimizer: GPORCA + -> Limit + -> Index Only Scan using timestamptzidx on timestamptztmp + Order By: (a <-> 'Tue Dec 18 04:59:54 2018 PST'::timestamp with time zone) (7 rows) SELECT a, a <-> '2018-12-18 10:59:54 GMT+2' FROM timestamptztmp ORDER BY a <-> '2018-12-18 10:59:54 GMT+2' LIMIT 3;